2013-07-09 6 views
5

मेरे पास एक बहुप्रचारित एप्लिकेशन है जो सिगचल के लिए एक हैंडलर स्थापित करता है जो बाल प्रक्रियाओं को लॉग और रीप करता है।
समस्या जो मैं देखता हूं तब शुरू होता है जब मैं system() पर कॉल कर रहा हूं। system() को बच्चे की प्रक्रिया समाप्त होने की प्रतीक्षा करनी पड़ती है और उसे बाहर निकलता है क्योंकि उसे निकास कोड की आवश्यकता होती है। यही कारण है कि यह SIGCHLD को ब्लॉक करने के लिए sigprocmask() पर कॉल करता है। लेकिन मेरे बहुप्रचारित अनुप्रयोग में, SIGCHLD को अभी भी एक अलग धागे में बुलाया जाता है और बच्चे को system() से पहले काटने का मौका मिलता है।लिनक्स: सिस्टम() + सिग्चल्ड हैंडलिंग + मल्टीथ्रेडिंग

क्या यह पॉज़िक्स में ज्ञात समस्या है?

इस बारे में मैंने सोचा कि सिगचल्ड को अन्य सभी धागे में अवरुद्ध करना है, लेकिन यह मेरे मामले में वास्तव में यथार्थवादी नहीं है क्योंकि सभी धागे सीधे मेरे कोड द्वारा बनाए जाते हैं।
मेरे पास अन्य विकल्प क्या हैं?

+0

सिस्टम() सिग्चेल्ड को किसी भी तरह से ब्लॉक नहीं करना चाहिए? और "SIGCHLD को एक अलग धागे में बुलाया जाता है" से आपका क्या मतलब है? – LostBoy

+0

शायद आप लाइब्रेरी (ग्लिब, पॉलीब, बूस्ट, क्यूटीकोर, ...) का उपयोग कर सकते हैं जो आपको सिस्टम (3) 'की तुलना में प्रक्रियाओं पर बेहतर नियंत्रण देता है। –

+1

@LostBoy 'system()' SIGCHLD को ब्लॉक करता है लेकिन केवल कॉलिंग थ्रेड के लिए। यह 'sigprocmask()' का उपयोग करता है जिसे "प्रक्रिया में प्रत्येक थ्रेड के अपने सिग्नल मास्क के रूप में दस्तावेज किया गया है।" – shoosh

उत्तर

3

हां, यह एक ज्ञात (या कम से कम दृढ़ता से सूचित) समस्या है।

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

(system() के लिए दस्तावेज़ से, जोर जोड़ा।)

तो, POSIXly आप, भाग्य से बाहर हैं जब तक आपके कार्यान्वयन SIGCHLD क़तार में होता है। यदि ऐसा होता है, तो आप निश्चित रूप से उन पिडों का रिकॉर्ड रख सकते हैं जिन्हें आपने फोर्क किया था, और फिर केवल उन लोगों को प्राप्त करें जिन्हें आप उम्मीद कर रहे थे।

लिनक्सली भी, आप भाग्य से बाहर हैं, signalfd appears also to collapse multiple SIGCHLDs के रूप में।

यूनिक्स, हालांकि, आपके पास अपने बच्चों को प्रबंधित करने और तीसरे पक्ष के दिनचर्या को अनदेखा करने के लिए बहुत सी चालाक और बहुत चालाक तकनीक उपलब्ध हैं। विरासत पाइपों का I/O मल्टीप्लेक्सिंग सिग्चल्ड पकड़ने का एक विकल्प है, जैसा कि एक छोटी, समर्पित "स्पॉन-हेल्पर" का उपयोग कर रहा है ताकि आपकी फोर्किंग और एक अलग प्रक्रिया में काट सकें।

+1

मैं भी इसमें भाग लेता हूं। मल्टीथ्रेडेड लाइब्रेरी में यूनिक्स सिग्नल हैंडलिंग को सही करना असंभव लगता है, जो कि अन्य कोड में हस्तक्षेप नहीं करना चाहिए। सिग्नल हैंडलिंग और प्रोसेस हैंडल की बजाय प्रक्रिया आईडी का उपयोग दो महत्वपूर्ण क्षेत्र हैं जहां यूनिक्स मरम्मत से परे टूट गया है। – Lothar

3

चूंकि आपके पास धागे हैं जिन्हें आप नियंत्रित नहीं कर सकते हैं, इसलिए मैं आपको अपने स्वयं के कार्यान्वयन के साथ system() कॉल (और शायद popen() इत्यादि) को रोकने के लिए प्रीलोडेड लाइब्रेरी लिखने की सलाह देता हूं। मैं पुस्तकालय में भी आपके SIGCHLD हैंडलर को भी शामिल करूंगा।

आप env LD_PRELOAD=libwhatever.so yourprogram के माध्यम से अपने कार्यक्रम चलाने के लिए नहीं करना चाहते हैं, तो आप कुछ तरह

const char *libs; 

libs = getenv("LD_PRELOAD"); 
if (!libs || !*libs) { 
    setenv("LD_PRELOAD", "libwhatever.so", 1); 
    execv(argv[0], argv); 
    _exit(127); 
} 

अपने कार्यक्रम के शुरू में यह LD_PRELOAD उचित रूप से सेट के साथ ही फिर से निष्पादित करने के लिए जोड़ सकते हैं। (नोट पर विचार करने के quirks देखते हैं कि यदि आपके प्रोग्राम setuid या setgid है, जानकारी के लिए man ld.so देख विशेष रूप से, अगर libwhatever.so एक प्रणाली पुस्तकालय निर्देशिका में स्थापित नहीं है, तो आप एक पूर्ण पथ निर्दिष्ट करना होगा।।)

एक संभव दृष्टिकोण लंबित बच्चों के लॉकलेस सरणी (सी कंपाइलर द्वारा प्रदान किए गए परमाणु निर्मित इंसिन का उपयोग करना) का उपयोग करना होगा।waitpid() के बजाय, आपके system() कार्यान्वयन में से एक प्रविष्टि आवंटित करता है, वहां बच्चे पीआईडी ​​चिपकाता है, और बच्चे को waitpid() पर कॉल करने के बजाय बाहर निकलने के लिए एक सेमफोर पर इंतजार करता है।

#define _GNU_SOURCE 
#define _POSIX_C_SOURCE 200809L 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/wait.h> 
#include <fcntl.h> 
#include <signal.h> 
#include <semaphore.h> 
#include <dlfcn.h> 
#include <errno.h> 

/* Maximum number of concurrent children waited for. 
*/ 
#define MAX_CHILDS 256 

/* Lockless array of child processes waited for. 
*/ 
static pid_t child_pid[MAX_CHILDS] = { 0 }; /* 0 is not a valid PID */ 
static sem_t child_sem[MAX_CHILDS]; 
static int child_status[MAX_CHILDS]; 

/* Helper function: allocate a child process. 
* Returns the index, or -1 if all in use. 
*/ 
static inline int child_get(const pid_t pid) 
{ 
    int i = MAX_CHILDS; 
    while (i-->0) 
     if (__sync_bool_compare_and_swap(&child_pid[i], (pid_t)0, pid)) { 
      sem_init(&child_sem[i], 0, 0); 
      return i; 
     } 
    return -1; 
} 

/* Helper function: release a child descriptor. 
*/ 
static inline void child_put(const int i) 
{ 
    sem_destroy(&child_sem[i]); 
    __sync_fetch_and_and(&child_pid[i], (pid_t)0); 
} 

/* SIGCHLD signal handler. 
* Note: Both waitpid() and sem_post() are async-signal safe. 
*/ 
static void sigchld_handler(int signum __attribute__((unused)), 
          siginfo_t *info __attribute__((unused)), 
          void *context __attribute__((unused))) 
{ 
    pid_t p; 
    int status, i; 

    while (1) { 
     p = waitpid((pid_t)-1, &status, WNOHANG); 
     if (p == (pid_t)0 || p == (pid_t)-1) 
      break; 

     i = MAX_CHILDS; 
     while (i-->0) 
      if (p == __sync_fetch_and_or(&child_pid[i], (pid_t)0)) { 
       child_status[i] = status; 
       sem_post(&child_sem[i]); 
       break; 
      } 

     /* Log p and status? */ 
    } 
} 

/* Helper function: close descriptor, without affecting errno. 
*/ 
static inline int closefd(const int fd) 
{ 
    int result, saved_errno; 

    if (fd == -1) 
     return EINVAL; 

    saved_errno = errno; 

    do { 
     result = close(fd); 
    } while (result == -1 && errno == EINTR); 
    if (result == -1) 
     result = errno; 
    else 
     result = 0; 

    errno = saved_errno; 

    return result; 
} 

/* Helper function: Create a close-on-exec socket pair. 
*/ 
static int commsocket(int fd[2]) 
{ 
    int result; 

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) { 
     fd[0] = -1; 
     fd[1] = -1; 
     return errno; 
    } 

    do { 
     result = fcntl(fd[0], F_SETFD, FD_CLOEXEC); 
    } while (result == -1 && errno == EINTR); 
    if (result == -1) { 
     closefd(fd[0]); 
     closefd(fd[1]); 
     return errno; 
    } 

    do { 
     result = fcntl(fd[1], F_SETFD, FD_CLOEXEC); 
    } while (result == -1 && errno == EINTR); 
    if (result == -1) { 
     closefd(fd[0]); 
     closefd(fd[1]); 
     return errno; 
    } 

    return 0; 
} 

/* New system() implementation. 
*/ 
int system(const char *command) 
{ 
    pid_t child; 
    int  i, status, commfd[2]; 
    ssize_t n; 

    /* Allocate the child process. */ 
    i = child_get((pid_t)-1); 
    if (i < 0) { 
     /* "fork failed" */ 
     errno = EAGAIN; 
     return -1; 
    } 

    /* Create a close-on-exec socket pair. */ 
    if (commsocket(commfd)) { 
     child_put(i); 
     /* "fork failed" */ 
     errno = EAGAIN; 
     return -1; 
    } 

    /* Create the child process. */ 
    child = fork(); 
    if (child == (pid_t)-1) 
     return -1; 

    /* Child process? */ 
    if (!child) { 
     char *args[4] = { "sh", "-c", (char *)command, NULL }; 

     /* If command is NULL, return 7 if sh is available. */ 
     if (!command) 
      args[2] = "exit 7"; 

     /* Close parent end of comms socket. */ 
     closefd(commfd[0]); 

     /* Receive one char before continuing. */ 
     do { 
      n = read(commfd[1], &status, 1); 
     } while (n == (ssize_t)-1 && errno == EINTR); 
     if (n != 1) { 
      closefd(commfd[1]); 
      _exit(127); 
     } 

     /* We won't receive anything else. */ 
     shutdown(commfd[1], SHUT_RD); 

     /* Execute the command. If successful, this closes the comms socket. */ 
     execv("/bin/sh", args); 

     /* Failed. Return the errno to the parent. */ 
     status = errno; 
     { 
      const char  *p = (const char *)&status; 
      const char *const q = (const char *)&status + sizeof status; 

      while (p < q) { 
       n = write(commfd[1], p, (size_t)(q - p)); 
       if (n > (ssize_t)0) 
        p += n; 
       else 
       if (n != (ssize_t)-1) 
        break; 
       else 
       if (errno != EINTR) 
        break; 
      } 
     } 

     /* Explicitly close the socket pair. */ 
     shutdown(commfd[1], SHUT_RDWR); 
     closefd(commfd[1]); 
     _exit(127); 
    } 

    /* Parent process. Close the child end of the comms socket. */ 
    closefd(commfd[1]); 

    /* Update the child PID in the array. */ 
    __sync_bool_compare_and_swap(&child_pid[i], (pid_t)-1, child); 

    /* Let the child proceed, by sending a char via the socket. */ 
    status = 0; 
    do { 
     n = write(commfd[0], &status, 1); 
    } while (n == (ssize_t)-1 && errno == EINTR); 
    if (n != 1) { 
     /* Release the child entry. */ 
     child_put(i); 
     closefd(commfd[0]); 

     /* Kill the child. */ 
     kill(child, SIGKILL); 

     /* "fork failed". */ 
     errno = EAGAIN; 
     return -1; 
    } 

    /* Won't send anything else over the comms socket. */ 
    shutdown(commfd[0], SHUT_WR); 

    /* Try reading an int from the comms socket. */ 
    { 
     char  *p = (char *)&status; 
     char *const q = (char *)&status + sizeof status; 

     while (p < q) { 
      n = read(commfd[0], p, (size_t)(q - p)); 
      if (n > (ssize_t)0) 
       p += n; 
      else 
      if (n != (ssize_t)-1) 
       break; 
      else 
      if (errno != EINTR) 
       break; 
     } 

     /* Socket closed with nothing read? */ 
     if (n == (ssize_t)0 && p == (char *)&status) 
      status = 0; 
     else 
     if (p != q) 
      status = EAGAIN; /* Incomplete error code, use EAGAIN. */ 

     /* Close the comms socket. */ 
     shutdown(commfd[0], SHUT_RDWR); 
     closefd(commfd[0]); 
    } 

    /* Wait for the command to complete. */ 
    sem_wait(&child_sem[i]); 

    /* Did the command execution fail? */ 
    if (status) { 
     child_put(i); 
     errno = status; 
     return -1; 
    } 

    /* Command was executed. Return the exit status. */ 
    status = child_status[i]; 
    child_put(i); 

    /* If command is NULL, then the return value is nonzero 
    * iff the exit status was 7. */ 
    if (!command) { 
     if (WIFEXITED(status) && WEXITSTATUS(status) == 7) 
      status = 1; 
     else 
      status = 0; 
    } 

    return status; 
} 

/* Library initialization. 
* Sets the sigchld handler, 
* makes sure pthread library is loaded, and 
* unsets the LD_PRELOAD environment variable. 
*/ 
static void init(void) __attribute__((constructor)); 
static void init(void) 
{ 
    struct sigaction act; 
    int    saved_errno; 

    saved_errno = errno; 

    sigemptyset(&act.sa_mask); 
    act.sa_sigaction = sigchld_handler; 
    act.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_SIGINFO; 

    sigaction(SIGCHLD, &act, NULL); 

    (void)dlopen("libpthread.so.0", RTLD_NOW | RTLD_GLOBAL); 

    unsetenv("LD_PRELOAD"); 

    errno = saved_errno; 
} 

आप के रूप में कहते हैं कि child.c ऊपर सहेजते हैं, तो आप इसे libchild.so में संकलित कर सकते हैं

gcc -W -Wall -O3 -fpic -fPIC -c child.c -lpthread 
gcc -W -Wall -O3 -shared -Wl,-soname,libchild.so child.o -ldl -lpthread -o libchild.so 

का उपयोग कर आप एक परीक्षण कार्यक्रम कि system() कॉल करता है, तो:

यहाँ एक उदाहरण दिया गया है विभिन्न धागे में, आप system()

का उपयोग करके इसे system() इंटरऑपोज़ (और बच्चों को स्वचालित रूप से प्राप्त किया गया) के साथ चला सकते हैं। 0
env LD_PRELOAD=/path/to/libchild.so test-program 

ध्यान दें कि पर वास्तव में क्या उन धागे जो आपके नियंत्रण में नहीं हैं करते हैं निर्भर करता है, आप इतने पर signal(), sigaction(), sigprocmask(), pthread_sigmask() हुआ था और इसमें उन धागे परिवर्तन नहीं है सुनिश्चित करने के लिए आगे काम करता है, जड़ना करना पड़ सकता है आपके SIGCHLD हैंडलर का स्वभाव (libchild.so लाइब्रेरी द्वारा स्थापित करने के बाद)।

उन बाहर के नियंत्रण धागे popen() उपयोग करते हैं, आपको लगता है कि लगाना कर सकते हैं (और pclose()) बहुत समान कोड के साथ ऊपर system() के लिए, बस दो भागों में विभाजित कर दिया।

(अगर आप सोच रहे हैं, तो क्यों मेरे system() कोड माता पिता की प्रक्रिया के लिए exec() विफलता की रिपोर्ट करने लगती है, तो है क्योंकि मैं आम तौर पर इस कोड है कि तार की एक सरणी के रूप में आदेश लेता है का एक प्रकार का उपयोग है, इस तरह से इसे सही ढंग से करता है, तो रिपोर्ट आदेश नहीं मिला था, अपर्याप्त विशेषाधिकारों के कारण निष्पादित नहीं किया जा सका, आदि। इस विशेष मामले में आदेश हमेशा /bin/sh होता है। हालांकि, चूंकि संचार सॉकेट की आवश्यकता होती है, वैसे भी बच्चे के बाहर निकलने के बीच रेसिंग से बचने और अद्यतित होने के लिए * Child_pid [] * सरणी में पीआईडी, मैंने "अतिरिक्त" कोड छोड़ने का फैसला किया।)

+0

यह बहुत अच्छी चीज है, धन्यवाद।मैं जो कर रहा हूं वह समान है लेकिन थोड़ा सा सरल है। 'ओवरराइड' 'सिस्टम()' में मैंने एक परमाणु बूलियन सेट किया है जो सिग्चेल्ड को ज़ोंबी काटने से रोकता है और इसके बजाय, मूल 'सिस्टम()' सिरों के ठीक बाद किसी भी शेष लाश काटता है – shoosh

0

उन लोगों के लिए जो अभी भी उत्तर की तलाश में हैं, इस समस्या को हल करने का एक आसान तरीका है:

सिग्चेड हैंडलर को झंडे के साथ वेटिड कॉल का उपयोग करने के लिए लिखना WNOHANG | उन्हें वापस लेने से पहले बच्चे के पीआईडी ​​की जांच करने के लिए WNOWAIT। आप कमांड नाम के लिए वैकल्पिक रूप से/proc/pID/stat (या समान ओएस इंटरफ़ेस) की जांच कर सकते हैं।

संबंधित मुद्दे