c - माता-पिता निकलने के बाद बच्चे की प्रक्रिया कैसे मरें?




linux unix (16)

SIGINT को पकड़ने के लिए एक जाल हैंडलर स्थापित करें, जो कि अभी भी जीवित है, तो आपके बच्चे की प्रक्रिया को मार देता है, हालांकि अन्य पोस्टर सही हैं कि यह सिगकिल नहीं पकड़ेगा।

अनन्य एक्सेस के साथ एक .lockfile खोलें और इसे खोलने की कोशिश कर रहे बच्चे के पोल पर - यदि खुली सफल हो, तो बच्चे की प्रक्रिया से बाहर निकलना चाहिए

मान लीजिए मेरे पास एक प्रक्रिया है जो वास्तव में एक बच्चे की प्रक्रिया पैदा करती है। अब जब माता-पिता की प्रक्रिया किसी भी कारण से (आमतौर पर या असामान्य रूप से, हत्या से, ^ सी, असफलता या किसी और चीज के लिए निकलती है) मैं चाहता हूं कि बच्चे की प्रक्रिया मर जाए। यह सही तरीके से कैसे करें?

स्टैक ओवरफ्लो पर कुछ समान प्रश्न:

विंडोज के लिए स्टैक ओवरफ्लो पर कुछ समान प्रश्न:


अगर माता-पिता मर जाते हैं, तो अनाथों का पीपीआईडी ​​1 में बदल जाता है - आपको केवल अपना खुद का पीपीआईडी ​​जांचना होगा। एक तरह से, यह उपरोक्त वर्णित मतदान है। यहां के लिए खोल टुकड़ा है:

check_parent () {
      parent=`ps -f|awk '$2=='$PID'{print $3 }'`
      echo "parent:$parent"
      let parent=$parent+0
      if [[ $parent -eq 1 ]]; then
        echo "parent is dead, exiting"
        exit;
      fi
}


PID=$$
cnt=0
while [[ 1 = 1 ]]; do
  check_parent
  ... something
done

कुछ पोस्टर पहले ही पाइप और kqueue उल्लेख कर kqueue । वास्तव में आप socketpair() कॉल द्वारा जुड़े यूनिक्स डोमेन सॉकेट की एक जोड़ी भी बना सकते हैं। सॉकेट प्रकार SOCK_STREAM होना चाहिए।

मान लीजिए कि आपके पास दो सॉकेट फ़ाइल डिस्क्रिप्टर fd1, fd2 हैं। अब बाल प्रक्रिया बनाने के लिए fork() , जो एफडीएस का वारिस होगा। माता-पिता में आप fd2 को बंद करते हैं और बच्चे में आप fd1 बंद करते हैं। अब प्रत्येक प्रक्रिया POLLIN घटना के लिए अपने स्वयं के अंत में शेष खुली एफडी poll() को poll() कर सकती है। जब तक प्रत्येक पक्ष सामान्य जीवनकाल के दौरान स्पष्ट रूप से close() नहीं होता है, तो आप निश्चित रूप से सुनिश्चित हो सकते हैं कि POLLHUP ध्वज को दूसरे की समाप्ति को इंगित करना चाहिए (कोई फर्क नहीं पड़ता या नहीं)। इस घटना के बारे में अधिसूचित होने पर, बच्चा तय कर सकता है कि क्या करना है (उदाहरण के लिए मरना)।

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    int sv[2];        /* sv[0] for parent, sv[1] for child */
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    pid_t pid = fork();

    if ( pid > 0 ) {  /* parent */
        close(sv[1]);
        fprintf(stderr, "parent: pid = %d\n", getpid());
        sleep(100);
        exit(0);

    } else {          /* child */
        close(sv[0]);
        fprintf(stderr, "child: pid = %d\n", getpid());

        struct pollfd mon;
        mon.fd = sv[1];
        mon.events = POLLIN;

        poll(&mon, 1, -1);
        if ( mon.revents & POLLHUP )
            fprintf(stderr, "child: parent hung up\n");
        exit(0);
    }
}

आप उपरोक्त सबूत-ऑफ-अवधारणा कोड को संकलित करने का प्रयास कर सकते हैं, और इसे टर्मिनल में चला सकते हैं जैसे ./a.out & । विभिन्न सिग्नल द्वारा पैरेंट पीआईडी ​​को मारने के साथ प्रयोग करने के लिए आपके पास लगभग 100 सेकंड हैं, या यह बस बाहर निकल जाएगा। किसी भी मामले में, आपको "बच्चा: अभिभावक लटका" संदेश देखना चाहिए।

SIGPIPE हैंडलर का उपयोग कर विधि के मुकाबले, इस विधि को write() कॉल करने की आवश्यकता नहीं है।

यह विधि सममित है , यानी प्रक्रियाएं एक दूसरे के अस्तित्व की निगरानी के लिए एक ही चैनल का उपयोग कर सकती हैं।

यह समाधान केवल POSIX फ़ंक्शन को कॉल करता है। मैंने इसे लिनक्स और फ्रीबीएसडी में करने की कोशिश की। मुझे लगता है कि इसे अन्य यूनिक्स पर काम करना चाहिए लेकिन मैंने वास्तव में परीक्षण नहीं किया है।

यह भी देखें:

  • लिनक्स मैन पेजों के unix(7) , लिनक्स पर फ्रीबीएसडी, poll(2) , socketpair(2) , socket(7) लिए unix(4)

क्या बच्चे की प्रक्रिया में पैरेंट प्रक्रिया से / पाइप है? यदि ऐसा है, तो लिखते समय आपको एक सिगिपिप प्राप्त होगा, या पढ़ने के दौरान ईओएफ प्राप्त करें - इन स्थितियों का पता लगाया जा सकता है।


पूर्णता के लिए। मैक ओएस एक्स पर आप kqueue का उपयोग कर सकते हैं:

void noteProcDeath(
    CFFileDescriptorRef fdref, 
    CFOptionFlags callBackTypes, 
    void* info) 
{
    // LOG_DEBUG(@"noteProcDeath... ");

    struct kevent kev;
    int fd = CFFileDescriptorGetNativeDescriptor(fdref);
    kevent(fd, NULL, 0, &kev, 1, NULL);
    // take action on death of process here
    unsigned int dead_pid = (unsigned int)kev.ident;

    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example

    int our_pid = getpid();
    // when our parent dies we die as well.. 
    LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
    exit(EXIT_SUCCESS);
}


void suicide_if_we_become_a_zombie(int parent_pid) {
    // int parent_pid = getppid();
    // int our_pid = getpid();
    // LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);

    int fd = kqueue();
    struct kevent kev;
    EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
    kevent(fd, &kev, 1, NULL, 0, NULL);
    CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

भले ही 7 साल बीत चुके हैं, मैंने अभी इस मुद्दे में भाग लिया है क्योंकि मैं स्प्रिंगबूट एप्लिकेशन चला रहा हूं जिसे विकास के दौरान वेबपैक-डीवी-सर्वर शुरू करने की आवश्यकता है और बैकएंड प्रक्रिया बंद होने पर इसे मारने की जरूरत है।

मैं Runtime.getRuntime().addShutdownHook का उपयोग करने का प्रयास करता हूं Runtime.getRuntime().addShutdownHook लेकिन यह विंडोज 10 पर काम करता है लेकिन विंडोज 7 पर नहीं।

मैंने इसे समर्पित थ्रेड का उपयोग करने के लिए बदल दिया है जो प्रक्रिया को छोड़ने या InterruptedException एक्सेप्शन के लिए इंतजार कर रहा है जो कि विंडोज संस्करणों पर सही ढंग से काम करता है।

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}

मुझे लगता है कि बच्चे और माता-पिता के बीच एक पाइप बनाने का एक त्वरित और गंदा तरीका है। जब माता-पिता निकलते हैं, तो बच्चों को एक सिगिप प्राप्त होगा।


मुझे विश्वास नहीं है कि यह गारंटी देना संभव है कि केवल मानक POSIX कॉल का उपयोग करना। वास्तविक जीवन की तरह, एक बार जब बच्चा पैदा होता है, तो इसका अपना जीवन होता है।

माता-पिता की प्रक्रिया के लिए संभवतः सबसे संभावित समाप्ति घटनाओं को पकड़ना संभव है, और उस बिंदु पर बाल प्रक्रिया को मारने का प्रयास करना संभव है, लेकिन वहां हमेशा कुछ ऐसा नहीं किया जाता है जिन्हें पकड़ा नहीं जा सकता है।

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

समानता का विस्तार करने के लिए - ऐसा करने का एकमात्र अन्य मानक तरीका बच्चे के लिए आत्महत्या करना है जब उसे पता चलता है कि अब उसके माता-पिता नहीं हैं।

prctl(2) साथ ऐसा करने का एकमात्र तरीका है - अन्य उत्तरों देखें।


मैं टर्मिनल नियंत्रण और सत्रों का दुरुपयोग करके 3 प्रक्रियाओं के साथ एक पोर्टेबल, गैर-मतदान समाधान करने में कामयाब रहा। यह मानसिक हस्तमैथुन है, लेकिन काम करता है।

चाल है:

  • प्रक्रिया ए शुरू हो गया है
  • प्रक्रिया ए एक पाइप पी बनाता है (और इससे कभी नहीं पढ़ता है)
  • प्रक्रिया बी में एक कांटा प्रक्रिया बी
  • प्रक्रिया बी एक नया सत्र बनाता है
  • प्रक्रिया बी उस नए सत्र के लिए वर्चुअल टर्मिनल आवंटित करता है
  • प्रक्रिया बी जब बच्चे बाहर निकलता है तो मरने के लिए सिग्चाल्ड हैंडलर स्थापित करता है
  • प्रक्रिया बी एक सिगिप हैंडलर सेट करता है
  • प्रक्रिया सी में प्रक्रिया बी फोर्क्स सी
  • प्रक्रिया सी जो भी उसे चाहिए (उदाहरण के लिए exec () unmodified बाइनरी है या जो भी तर्क चलता है)
  • प्रक्रिया बी पाइप पी लिखता है (और उस तरह से ब्लॉक)
  • प्रक्रिया बी पर एक प्रतीक्षा () एस बी और जब यह मर जाता है बाहर निकलता है

उस तरफ:

  • यदि प्रक्रिया ए मर जाती है: प्रक्रिया बी को एक सिगिप हो जाता है और मर जाता है
  • यदि प्रक्रिया बी मर जाती है: प्रक्रिया ए का इंतजार () रिटर्न और मर जाता है, प्रक्रिया सी को एक SUPUP मिलती है (क्योंकि टर्मिनल संलग्न समूह के साथ सत्र के सत्र नेता मर जाते हैं, अग्रभूमि प्रक्रिया समूह में सभी प्रक्रियाओं को एक SIGHUP मिलता है)
  • यदि प्रक्रिया सी मर जाती है: प्रक्रिया बी को सिग्चाल्ड मिल जाता है और मर जाता है, इसलिए प्रक्रिया ए मर जाती है

कमियों:

  • प्रक्रिया सी SIGHUP संभाल नहीं कर सकते हैं
  • प्रक्रिया सी एक अलग सत्र में चलाया जाएगा
  • process C can't use session/process group API because it'll break the brittle setup
  • creating a terminal for every such operation is not the best idea ever

मैंने इसे "बच्चे" में "मूल" कोड और "मूल" में "स्पॉन्ड" कोड चलाकर अतीत में हासिल किया है (यानी: आप fork() बाद परीक्षण की सामान्य समझ को उलट देते हैं)। फिर "spawned" कोड में SIGCHLD जाल ...

आपके मामले में संभव नहीं हो सकता है, लेकिन जब यह काम करता है प्यारा हो सकता है।


यदि यह किसी और के लिए प्रासंगिक है, तो जब मैंने सी ++ से फोर्क की गई बाल प्रक्रियाओं में जेवीएम उदाहरणों को जन्म दिया, तो माता-पिता की प्रक्रिया पूरी होने के बाद मैं जेवीएम उदाहरणों को सही तरीके से समाप्त करने का एकमात्र तरीका प्राप्त कर सकता था। उम्मीद है कि अगर कोई ऐसा करने का सबसे अच्छा तरीका नहीं है तो कोई टिप्पणी में फीडबैक प्रदान कर सकता है।

1) prctl(PR_SET_PDEATHSIG, SIGHUP) माध्यम से जावा ऐप लॉन्च करने से पहले सुझाए गए फोर्क किए गए बाल प्रक्रिया पर prctl(PR_SET_PDEATHSIG, SIGHUP) को कॉल करें, और

2) जावा एप्लिकेशन में शटडाउन हुक जोड़ें जो उसके पैरेंट पीआईडी ​​के बराबर होने तक चुनाव करता है, फिर एक कठिन Runtime.getRuntime().halt(0) । मतदान एक अलग खोल खोलकर किया जाता है जो ps कमांड चलाता है (देखें: मैं जावा में अपना पीआईडी ​​कैसे ढूंढूं या लिनक्स पर जेआरबीई कैसे प्राप्त करूं? )।

130118 संपादित करें:

ऐसा लगता है कि एक मजबूत समाधान नहीं था। मैं अभी भी क्या हो रहा है की बारीकियों को समझने के लिए थोड़ा संघर्ष कर रहा हूं, लेकिन स्क्रीन / एसएसएच सत्रों में इन अनुप्रयोगों को चलाने के दौरान भी मुझे कभी-कभी अनाथ JVM प्रक्रियाएं मिल रही थीं।

जावा ऐप में पीपीआईडी ​​के लिए मतदान करने के बजाय, मैंने बस शट डाउन हुक को उपरोक्त के रूप में एक कठोर ठहराव के बाद सफाई का प्रदर्शन किया था। तब मैंने सुनिश्चित किया कि जब तक सब कुछ समाप्त करने का समय हो, तो बच्चे की प्रक्रिया पर सी ++ पेरेंट ऐप में waitpid का आह्वान करना सुनिश्चित करें। यह एक और मजबूत समाधान प्रतीत होता है, क्योंकि बाल प्रक्रिया यह सुनिश्चित करती है कि यह समाप्त हो जाए, जबकि अभिभावक मौजूदा संदर्भों का उपयोग करता है ताकि यह सुनिश्चित किया जा सके कि उसके बच्चे समाप्त हो जाएं। इसे पिछले समाधान से तुलना करें, जब माता-पिता की प्रक्रिया पूरी हो जाती है, और बच्चों को यह पता लगाने की कोशिश की जाती है कि क्या उन्हें समाप्त करने से पहले अनाथाश्रम किया गया था।


यह समाधान मेरे लिए काम किया:

  • बच्चे को stdin पाइप पास करें - आपको स्ट्रीम में कोई भी डेटा लिखना नहीं है।
  • बच्चा ईएफओ तक stdin से अनिश्चित काल तक पढ़ता है। एक ईओएफ सिग्नल जो माता-पिता चले गए हैं।
  • यह पता लगाने के लिए मूर्खतापूर्ण और पोर्टेबल तरीका है जब माता-पिता चले गए हैं। यहां तक ​​कि अगर माता-पिता दुर्घटनाग्रस्त हो जाते हैं, तो ओएस पाइप बंद कर देगा।

यह एक कार्यकर्ता-प्रकार की प्रक्रिया के लिए था जिसका अस्तित्व केवल तभी हुआ जब माता-पिता जीवित थे।


लिनक्स के तहत, आप बच्चे में अभिभावक मृत्यु संकेत स्थापित कर सकते हैं, उदाहरण के लिए:

#include <sys/prctl.h>
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h>  // perror()

// ...

pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
  ; // continue parent execution
} else {
  int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
  if (r == -1) { perror(0); exit(1); }
  // test in case the original parent exited just
  // before the prctl() call
  if (getppid() != ppid_before_fork)
    exit(1);
  // continue child execution ...

ध्यान दें कि कांटे से पहले माता-पिता प्रक्रिया आईडी को संग्रहित करना और prctl() बाद बच्चे में परीक्षण करना prctl() बीच दौड़ की स्थिति को समाप्त करता है और बच्चे को बुलाए जाने वाले प्रक्रिया के बाहर निकलता है।

यह भी ध्यान रखें कि बच्चे के माता-पिता की मृत्यु संकेत स्वयं के नव निर्मित बच्चों में साफ़ हो गया है। यह एक execve() प्रभावित नहीं है।

यह परीक्षण सरल हो सकता है अगर हम निश्चित हैं कि सभी orphans को अपनाने के प्रभारी सिस्टम प्रक्रिया में पीआईडी ​​1 है:

pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
  ; // continue parent execution
} else {
  int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
  if (r == -1) { perror(0); exit(1); }
  // test in case the original parent exited just
  // before the prctl() call
  if (getppid() == 1)
    exit(1);
  // continue child execution ...

उस सिस्टम प्रक्रिया पर निर्भर होने पर पीआईडी ​​1 पोर्टेबल नहीं है, हालांकि। POSIX.1-2008 निर्दिष्ट करता है :

कॉलिंग प्रक्रिया की सभी मौजूदा बाल प्रक्रियाओं और ज़ोंबी प्रक्रियाओं की मूल प्रक्रिया आईडी को कार्यान्वयन-परिभाषित सिस्टम प्रक्रिया की प्रक्रिया आईडी पर सेट किया जाएगा। यही है, इन प्रक्रियाओं को एक विशेष प्रणाली प्रक्रिया द्वारा विरासत में प्राप्त किया जाएगा।

परंपरागत रूप से, सभी अनाथों को अपनाने वाली प्रणाली प्रक्रिया पीआईडी ​​1 है, यानी init - जो सभी प्रक्रियाओं का पूर्वज है।

prctl() या FreeBSD जैसी आधुनिक प्रणालियों पर एक और प्रक्रिया में वह भूमिका हो सकती है। उदाहरण के लिए, लिनक्स पर, एक प्रक्रिया prctl() को सिस्टम प्रक्रिया के रूप में स्थापित करने के लिए कॉल कर सकती है जो इसके किसी भी वंशज के सभी अनाथों को प्राप्त करती है (सीएफ। फेडोरा 25 पर एक example )।


POSIX तहत, exit() , _exit() और _Exit() फ़ंक्शंस को परिभाषित किया गया है:

  • यदि प्रक्रिया एक नियंत्रण प्रक्रिया है, तो SIGHUP सिग्नल कॉलिंग प्रक्रिया से संबंधित नियंत्रण टर्मिनल के अग्रभूमि प्रक्रिया समूह में प्रत्येक प्रक्रिया को भेजा जाएगा।

इसलिए, यदि आप अपने प्रक्रिया समूह के लिए मूल प्रक्रिया को नियंत्रित करने की प्रक्रिया की व्यवस्था करते हैं, तो बच्चे को बाहर निकलने पर एक SUPUP सिग्नल प्राप्त करना चाहिए। मुझे पूरा यकीन नहीं है कि जब माता-पिता दुर्घटनाग्रस्त हो जाते हैं, लेकिन मुझे लगता है कि यह करता है। निश्चित रूप से, गैर-दुर्घटना के मामलों के लिए, इसे ठीक काम करना चाहिए।

ध्यान दें कि पूरी तस्वीर प्राप्त करने के लिए आपको बेस डिफिनिशन (परिभाषाएं) अनुभाग के साथ-साथ exit() लिए सिस्टम सेवा जानकारी exit() और setsid() और setpgrp() सहित बहुत सारे अच्छे प्रिंट को पढ़ना पड़ सकता है। (तो क्या मैं!)


Historically, from UNIX v7, the process system has detected orphanity of processes by checking a process' parent id. As I say, historically, the init(8) system process is a special process by only one reason: It cannot die. It cannot die because the kernel algorithm to deal with assigning a new parent process id, depends on this fact. when a process executes its exit(2) call (by means of a process system call or by external task as sending it a signal or the like) the kernel reassigns all children of this process the id of the init process as their parent process id. This leads to the most easy test, and most portable way of knowing if a process has got orphan. Just check the result of the getppid(2) system call and if it is the process id of the init(2) process then the process got orphan before the system call.

Two issues emerge from this approach that can lead to issues:

  • first, we have the possibility of changing the init process to any user process, so How can we assure that the init process will always be parent of all orphan processes? Well, in the exit system call code there's a explicit check to see if the process executing the call is the init process (the process with pid equal to 1) and if that's the case, the kernel panics (It should not be able anymore to maintain the process hierarchy) so it is not permitted for the init process to do an exit(2) call.
  • second, there's a race condition in the basic test exposed above. Init process' id is assumed historically to be 1 , but that's not warranted by the POSIX approach, that states (as exposed in other response) that only a system's process id is reserved for that purpose. Almost no posix implementation does this, and you can assume in original unix derived systems that having 1 as response of getppid(2) system call is enough to assume the process is orphan. Another way to check is to make a getppid(2) just after the fork and compare that value with the result of a new call. This simply doesn't work in all cases, as both call are not atomic together, and the parent process can die after the fork(2) and before the first getppid(2) system call. The process parent id only changes once, when its parent does an exit(2) call, so this should be enough to check if the getppid(2) result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children of init(8)`, but you can assume safely these processes as having no parent either (except when you substitute in a system the init process)

बच्चा कर्नेल से SIGHUP (या अन्य सिग्नल) देने के लिए कह सकता है जब माता-पिता prctl() syscall में विकल्प PR_SET_PDEATHSIG निर्दिष्ट करके मर जाता है:

prctl(PR_SET_PDEATHSIG, SIGHUP);

विवरण के लिए man 2 prctl देखें।

संपादित करें: यह केवल लिनक्स है





fork