Ebeveyn çıktıktan sonra çocuk süreci nasıl ölür?


209

Bir çocuk sürecini ortaya çıkaran bir sürecim olduğunu varsayalım. Şimdi ana süreç herhangi bir nedenle (normalde veya anormal olarak, kill, ^ C, iddia hatası veya başka bir şeyle) çıktığında, alt sürecin ölmesini istiyorum. Bunu nasıl doğru şekilde yapabilirim?


Stackoverflow hakkında benzer bir soru:


Windows için stackoverflow hakkında benzer bir soru :

Yanıtlar:


189

Çocuk sunmak için çekirdek sorabilir SIGHUP(veya başka sinyali) zaman seçeneğini belirterek ebeveyn ölür PR_SET_PDEATHSIGde prctl()böyle syscall:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Ayrıntılar man 2 prctliçin bakınız.

Düzenleme: Bu yalnızca Linux


5
Bu kötü bir çözümdür çünkü ebeveyn zaten ölmüş olabilir. Yarış kondisyonu. Doğru çözüm: stackoverflow.com/a/17589555/412080
Maxim Egorushkin

16
Bir cevap fakiri olarak adlandırmak çok hoş değil - bir yarış koşulu ele almasa bile. Yarış koşullarında nasıl kullanılacağına dair cevabımı görün prctl(). Btw, Maxim'in bağladığı cevap yanlış.
maxschlepzig

4
Bu sadece yanlış bir cevap. Sinyali, ana süreç sona erdiğinde değil, çatal adı verilen ipliğin öldüğü zamanda alt sürece gönderir.
Lothar

2
@ Lothar Bir çeşit kanıt görmek güzel olurdu. man prctldiyor: Çağıran sürecin üst süreç ölüm sinyalini arg2 olarak ayarlayın (temizlemek için 1..maxsig veya 0 aralığında bir sinyal değeri). Bu, üst öğe öldüğünde çağrı işleminin alacağı sinyaldir. Bu değer, bir set-user-ID veya set-group-ID ikili dosyası yürütülürken çatal (2) ve (Linux 2.4.36 / 2.6.23'ten beri) alt öğesi için temizlenir.
qrdl

1
@maxschlepzig Yeni bağlantı için teşekkürler. Önceki bağlantı geçersiz gibi görünüyor. Bu arada, yıllar sonra, ana tarafta seçenekleri ayarlamak için hala bir API yok. Ne yazık.
17'de rox

68

Aynı sorunu çözmeye çalışıyorum ve programımın OS X üzerinde çalışması gerektiğinden, yalnızca Linux çözümü benim için çalışmadı.

Bu sayfadaki diğer insanlarla aynı sonuca vardım - bir ebeveyn öldüğünde çocuğa bildirimde bulunmanın POSIX uyumlu bir yolu yoktur. Bu yüzden bir sonraki en iyi şeyi yaptım - çocuk anketi yapmak.

Bir üst süreç öldüğünde (herhangi bir nedenle) çocuğun üst süreci 1. işlem haline gelir. Çocuk periyodik olarak yoklarsa, üst öğesinin 1 olup olmadığını kontrol edebilir. Öyleyse, çocuk çıkmalıdır.

Bu harika değil, ama işe yarıyor ve bu sayfada başka bir yerde önerilen TCP soketi / kilit dosyası yoklama çözümlerinden daha kolay.


6
Mükemmel çözüm. Getppid () işlevini 1 döndürene ve ardından çıkana kadar sürekli olarak çağırır. Bu iyi ve şimdi de kullanıyorum. Pollig olmayan bir çözüm olsa iyi olurdu. Teşekkürler Schof.
neoneye

10
Sadece bilgi için, eğer bir bölgede iseniz Solaris üzerinde, gettpid()1 olmaz ama pidbölge zamanlayıcısını (işlem zsched) alır.
Patrick Schlüter

4
Herkes merak ediyorsa, Android sistemlerinde pid, ebeveyn öldüğünde 1 yerine 0 (işlem Sistemi pid'i) gibi görünüyor.
Rui Marques

2
Çatal () yapmadan önce bunu yapmanın daha sağlam ve platformdan bağımsız bir yoluna sahip olmak için getpid () ve çocuktan getppid () farklıysa çıkın.
Sebastien

2
Alt süreci kontrol etmezseniz bu işe yaramaz. Örneğin, find (1) 'i saran bir komut üzerinde çalışıyorum ve sargıcının herhangi bir nedenle ölmesi durumunda bulgunun öldüğünden emin olmak istiyorum.
Lucretiel

34

Ben geçmişte "alt" "orijinal" kodunu ve "üst" "spawned" kodunu çalıştırarak başardık (yani: sonra test olağan duygusu ters fork()). Sonra "spawned" kodu SIGCHLD tuzak ...

Sizin durumunuzda mümkün olmayabilir, ama çalışırken sevimli.


Çok güzel bir çözüm, teşekkürler! Şu anda kabul edilen daha genel, ancak sizinki daha taşınabilir.
Paweł Hajdan

1
Ebeveynte iş yapmanın en büyük sorunu, ebeveyn sürecini değiştiriyor olmanızdır. "Sonsuza kadar" çalışması gereken bir sunucu varsa, bu bir seçenek değildir.
Alexis Wilke

29

Alt işlemi değiştiremiyorsanız, aşağıdakine benzer bir şey deneyebilirsiniz:

int pipes[2];
pipe(pipes)
if (fork() == 0) {
    close(pipes[1]); /* Close the writer end in the child*/
    dup2(0, pipes[0]); /* Use reader end as stdin */
    exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}

close(pipes[0]); /* Close the reader end in the parent */

Bu, alt öğeyi iş denetimi etkinleştirilmiş bir kabuk işlemi içinde çalıştırır. Alt süreç arka planda üretilir. Mermi bir satırsonu (veya bir EOF) bekler ve çocuğu öldürür.

Ebeveyn öldüğünde - nedeni ne olursa olsun - borunun ucunu kapatacaktır. Çocuk kabuğu okumadan bir EOF alacak ve arka plandaki çocuk sürecini öldürmeye devam edecektir.


2
Güzel, ancak beş sistem çağrısı ve on satır kodda ortaya çıkan bir sh, bu kod performansları hakkında biraz şüpheci olmamı sağlıyor.
Oleiade

+1. Belirli bir dosya tanımlayıcısından okumak dup2için read -ubayrağı kullanarak stdin'i önleyebilir ve devralmayı önleyebilirsiniz . Ayrıca setpgid(0, 0), terminalde ^ C tuşuna basıldığında çıkmasını önlemek için alt öğeye a ekledim .
Greg Hewgill

dup2()Çağrının argüman sırası yanlış. Eğer pipes[0]stdin olarak kullanmak istiyorsanız dup2(pipes[0], 0)bunun yerine yazmak zorundasınız dup2(0, pipes[0]). Bu dup2(oldfd, newfd)çağrı daha önce açık olan bir newfd'yi kapatır.
maxschlepzig

@Oleiade, katılıyorum, özellikle doğmuş sh gerçek çocuk sürecini yürütmek için sadece başka bir çatal yapıyor ...
maxschlepzig

16

Linux altında, çocuğa bir üst ölüm sinyali yükleyebilirsiniz, örneğin:

#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#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 ...

Ana işlem kimliğinin çataldan önce saklanmasının ve daha sonra alt öğede test edilmesinin, alt çağıran işlemin prctl()arasındaki bir yarış durumunu prctl()ve bu işlemin çıkışını ortadan kaldırdığını unutmayın .

Ayrıca çocuğun yeni ölüm çocuklarında kendi ana ölüm sinyalinin silindiğine dikkat edin. Bir etkilenmez execve().

Tüm yetimleri kabul etmekle görevli sistem sürecinin PID 1'e sahip olduğundan eminsek , bu test basitleştirilebilir :

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

Bununla birlikte, bu sistem sürecine güvenmek initve PID 1'e sahip olmak taşınabilir değildir. POSIX.1-2008 şunları belirtir :

Çağıran sürecin tüm alt süreçleri ve zombi süreçlerinin üst süreç kimliği, uygulama tanımlı bir sistem işleminin süreç kimliğine ayarlanmalıdır. Yani, bu süreçler özel bir sistem süreci tarafından miras alınacaktır.

Geleneksel olarak, tüm yetimleri benimseyen sistem süreci PID 1'dir, yani init - tüm süreçlerin atasıdır.

Linux veya FreeBSD gibi modern sistemlerde başka bir işlemin rolü olabilir. Örneğin, Linux'ta, bir süreç prctl(PR_SET_CHILD_SUBREAPER, 1)kendisini soyundan herhangi birinin tüm yetimlerini devralan bir sistem süreci olarak kurmaya çağırabilir (bkz . Fedora 25'teki bir örnek ).


"Büyükbaba veya büyükannenin her zaman init süreci olduğundan eminsek, bu test basitleştirilebilir" anlamıyorum. Bir ana süreç öldüğünde, bir süreç büyükanne veya büyükbaba çocuğu değil, init sürecinin (pid 1) bir çocuğu olur, değil mi? Test her zaman doğru gözüküyor.
Johannes Schaub - litb


ilginç, teşekkürler. buna rağmen, büyükbaba veya büyükanne ile ne ilgisi olduğunu göremiyorum.
Johannes Schaub - litb

1
@ JohannesSchaub-litb, her zaman bir işlemin büyükanne / büyüklüğünün süreç olacağını varsayamazsınız init(8).... üstlenebileceğiniz tek şey, bir üst süreç öldüğünde üst kimliğinin değişeceğidir. Bu aslında bir sürecin hayatında bir kez olur .... ve sürecin ebeveyni öldüğü zamandır. Bunun tek bir istisnası vardır ve init(8)çocuklar içindir, ancak bundan init(8)asla korunmazsınız exit(2)(bu durumda çekirdek panikleri)
Luis Colorado

1
Ne yazık ki, bir çocuk bir iş parçacığından çatallanır ve daha sonra iş parçacığı çıkarsa, alt işlem SIGTERM alır.
rox

14

Bütünlük uğruna. MacOS'ta kqueue kullanabilirsiniz:

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

Bunu DISPATCH_SOURCE_PROC ve PROC_EXIT ile gönderim kaynaklarını kullanarak biraz daha hoş bir API ile yapabilirsiniz.
russbishop

Hangi nedenle olursa olsun, bu Mac'imin panik yapmasına neden oluyor. Bu kodla bir işlem yürütmenin% 50 şansı vardır, bu da fanların daha önce hiç duymadığım bir hızda dönmesine neden olur (süper hızlı) ve daha sonra mac kapanır. BU KODU İLE ÇOK DİKKATLİ OLUN .
Qix - MONICA

Görünüşe göre macOS'umda, ebeveyn çıktıktan sonra alt süreç otomatik olarak kapanıyor. Nedenini bilmiyorum.
Yi Lin Liu

@YiLinLiu iirc Ben NSTaskspawn kullandım veya posix. Bkz startTask: İşte benim kod işlevi github.com/neoneye/newton-commander-browse/blob/master/Classes/...
neoneye

11

Alt süreçte üst sürece giden / üst süreçten bir boru var mı? Eğer öyleyse, yazıyorsanız bir SIGPIPE alırsınız veya okurken EOF alırsınız - bu koşullar tespit edilebilir.


1
Bunun en azından OS X'te güvenilir bir şekilde gerçekleşmediğini
gördüm

Bu yanıtı eklemek için geldim. Bu kesinlikle bu dava için kullanacağım çözüm.
Dolda2000

Dikkat edilecek nokta: systemd, yönettiği hizmetlerde SIGPIPE'leri varsayılan olarak devre dışı bırakır, ancak yine de boru bağlantılarını kontrol edebilirsiniz. IgnoreSIGPIPE
jdizzle

11

Burada başka bir cevaptan esinlenerek aşağıdaki tüm POSIX çözümünü buldum. Genel fikir, ebeveyn ve çocuk arasında bir amacı olan bir ara süreç oluşturmaktır: Ebeveyn öldüğünde dikkat edin ve çocuğu açıkça öldürün.

Bu tür bir çözüm, alt öğedeki kod değiştirilemediğinde kullanışlıdır.

int p[2];
pipe(p);
pid_t child = fork();
if (child == 0) {
    close(p[1]); // close write end of pipe
    setpgid(0, 0); // prevent ^C in parent from stopping this process
    child = fork();
    if (child == 0) {
        close(p[0]); // close read end of pipe (don't need it here)
        exec(...child process here...);
        exit(1);
    }
    read(p[0], 1); // returns when parent exits for any reason
    kill(child, 9);
    exit(1);
}

Bu yöntemle iki küçük uyarı vardır:

  • Ara süreci kasten öldürürseniz, ebeveyn öldüğünde çocuk öldürülmez.
  • Çocuk ebeveynden önce çıkarsa, ara işlem, şimdi farklı bir sürece atıfta bulunabilecek orijinal çocuk pidini öldürmeye çalışır. (Bu, ara işlemde daha fazla kodla düzeltilebilir.)

Bir kenara kullandığım gerçek kod Python'da. İşte tamlık için:

def run(*args):
    (r, w) = os.pipe()
    child = os.fork()
    if child == 0:
        os.close(w)
        os.setpgid(0, 0)
        child = os.fork()
        if child == 0:
            os.close(r)
            os.execl(args[0], *args)
            os._exit(1)
        os.read(r, 1)
        os.kill(child, 9)
        os._exit(1)
    os.close(r)

Bir süre önce, IRIX altında, her ikisi arasında bir boruya sahip olduğum bir ebeveyn / çocuk şeması kullandığımı ve her ikisinin de ölmesi durumunda borudan okumanın bir SIGHUP oluşturduğunu unutmayın. Ara süreçlere gerek kalmadan çatal () 'ın çocuklarımı öldürdüğüm yol buydu.
Alexis Wilke

Sanırım ikinci uyarınız yanlış. Bir çocuğun pid, ebeveynine ait bir kaynaktır ve ebeveyn (ara işlem) üzerinde beklemeden (veya sona erdirip init üzerinde beklemeye izin verene kadar) serbest bırakılamaz / tekrar kullanılamaz.
R .. GitHub BUZA YARDIMCI DURDUR

7

Sadece standart POSIX çağrılarının kullanılmasını garanti etmenin mümkün olmadığına inanmıyorum. Gerçek hayat gibi, bir çocuk doğduğunda, kendine ait bir hayatı vardır.

Bu ise üst süreç mümkün olan en sonlandırma olaylarını yakalamak ve o noktada çocuk sürecini öldürmeye teşebbüs etmek mümkün, ama her zaman yakalanmış edilemeyeceğini bazı var.

Örneğin, hiçbir işlem a yakalayamaz SIGKILL. Çekirdek bu sinyali işlediğinde, belirtilen sürece herhangi bir bildirimde bulunmadan belirtilen işlemi öldürür.

Analojiyi genişletmek için - bunu yapmanın diğer tek standart yolu, çocuğun artık bir ebeveyni olmadığını tespit ettiğinde intihar etmektir.

Bunu yapmanın tek Linux yolu var prctl(2)- diğer cevaplara bakın.


6

Diğer insanların işaret ettiği gibi, ebeveyn çıkışları taşınabilir olmadığında ebeveyn pidine güvenmek 1 olur. Belirli bir üst işlem kimliğini beklemek yerine, kimliğin değişmesini bekleyin:

pit_t pid = getpid();
switch (fork())
{
    case -1:
    {
        abort(); /* or whatever... */
    }
    default:
    {
        /* parent */
        exit(0);
    }
    case 0:
    {
        /* child */
        /* ... */
    }
}

/* Wait for parent to exit */
while (getppid() != pid)
    ;

Tam hızda yoklamak istemiyorsanız, istediğiniz gibi bir mikro uyku ekleyin.

Bu seçenek benim için bir boru kullanmaktan veya sinyallere güvenmekten daha basit görünüyor.


Ne yazık ki, bu çözüm sağlam değil. İlk değeri almadan önce üst süreç ölürse ne olur? Çocuk asla çıkmayacak.
dgatwood

@dgatwood, ne demek istiyorsun?!? Birincisi getpid(), aramadan önce ebeveynte yapılır fork(). Ebeveyn bundan önce ölürse çocuk yoktur. Ne olabilir, bir süre ebeveynini yaşayan çocuk.
Alexis Wilke

Bu biraz uydurulmuş örnekte işe yarıyor, ancak gerçek dünya kodunda, çatalın neredeyse her zaman exec tarafından takip edilmesi ve yeni sürecin PPID'sini sorarak başlaması gerekiyor. Bu iki kontrol arasındaki sürede, eğer ebeveyn kaybolursa, çocuğun hiçbir fikri olmazdı. Ayrıca, hem üst hem de alt kod üzerinde kontrol sahibi olmanız olası değildir (ya da PPID'yi bağımsız değişken olarak geçirebilirsiniz). Genel bir çözüm olarak, bu yaklaşım pek işe yaramaz. Ve gerçekçi olarak, eğer UNIX benzeri bir işletim sistemi init 1 olmadan ortaya çıkarsa, o kadar çok şey kırılır ki, bunu kimsenin yaptığını hayal bile edemem.
dgatwood

pass parent pid, child için exec yaparken komut satırı bağımsız değişkenidir.
Nish

2
Tam hızda yoklama delilik.
maxschlepzig

4

Diğer posterlerin SIGKILL'i yakalamayacağı doğru olsa da, çocuk sürecinizi hala yaşıyorsa öldüren SIGINT'i yakalamak için bir tuzak işleyicisi takın .

Özel erişime sahip bir .lockfile açın ve çocuğun açmaya çalıştığı anketi açın - açık başarılı olursa, alt süreçten çıkılmalıdır


Ya da, çocuk kilit dosyasını ayrı bir iş parçacığında, engelleme modunda açabilir, bu durumda bu oldukça güzel ve temiz bir çözüm olabilir. Muhtemelen bazı taşınabilirlik sınırlamaları vardır.
Jean

4

Bu çözüm benim için çalıştı:

  • Stdin pipe'ı çocuğa geçirin - akışa herhangi bir veri yazmak zorunda değilsiniz.
  • Çocuk, stdin'den EOF'a kadar süresiz olarak okur. EOF, ebeveynin gittiğini gösterir.
  • Bu, ebeveynin ne zaman gittiğini tespit etmenin kusursuz ve taşınabilir bir yoludur. Ebeveyn çökse bile, OS boruyu kapatır.

Bu, varlığı yalnızca ebeveyn hayatta olduğunda mantıklı olan işçi tipi bir süreç içindi.


@SebastianJylanki Denediğimi hatırlamıyorum, ancak muhtemelen işe yarar çünkü ilkeller (POSIX akışları) işletim sistemleri arasında oldukça standarttır.
joonas.fi

3

Hızlı ve kirli bir yolun çocuk ve ebeveyn arasında bir boru oluşturmak olduğunu düşünüyorum. Ebeveyn çıktıktan sonra çocuklar SIGPIPE alacaktır.


SIGPIPE boru yakınına gönderilmez, sadece çocuk yazmaya yazmaya çalıştığında gönderilir.
Alcaro

3

Bazı posterler zaten boru ve kqueue. Aslında , çağrı ile bir çift bağlı Unix etki alanı soketi de oluşturabilirsiniz socketpair(). Soket tipi olmalıdır SOCK_STREAM.

Diyelim ki iki soket dosyası tanımlayıcısı fd1, fd2. Şimdi fork()fds devralınacak alt süreci oluşturmak için. Üst öğede fd2'yi ve alt öğede fd1'i kapatırsınız. Şimdi her işlem poll()kalan açık POLLINetkinlik için kendi ucunda olabilir . Her bir taraf close()normal kullanım ömrü boyunca açıkça fd'sini sağlamadığı sürece , bir POLLHUPbayrağın diğerinin sonlandırmasını göstermesi gerektiğinden emin olabilirsiniz (temiz olsun ya da olmasın). Bu olayın bildirilmesi üzerine, çocuk ne yapacağına karar verebilir (örneğin ölmek için).

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

Yukarıdaki kavram kanıtı kodunu derlemeyi deneyebilir ve benzeri bir terminalde çalıştırabilirsiniz ./a.out &. Ana PID'yi çeşitli sinyallerle öldürmeyi denemek için yaklaşık 100 saniyeniz var, yoksa çıkacak. Her iki durumda da, "child: parent as hung" mesajını görmelisiniz.

SIGPIPEİşleyici kullanan yöntemle karşılaştırıldığında , bu yöntem write()çağrıyı denemeyi gerektirmez .

Bu yöntem de simetriktir , yani işlemler birbirlerinin varlığını izlemek için aynı kanalı kullanabilir.

Bu çözüm yalnızca POSIX işlevlerini çağırır. Bunu Linux ve FreeBSD'de denedim. Diğer Unix'lerde çalışması gerektiğini düşünüyorum ama gerçekten test etmedim.

Ayrıca bakınız:

  • unix(7)Linux kılavuz sayfalarının, unix(4)FreeBSD için, poll(2), socketpair(2), socket(7)Linux üzerinde.

Çok güzel, bunun güvenilirlik sorunları olup olmadığını gerçekten merak ediyorum. Bunu üretimde test ettiniz mi? Farklı uygulamalarla mı?
Aktau

@Aktau, bu hilenin Python eşdeğerini bir Linux programında kullanıyorum. İhtiyacım vardı çünkü çocuğun çalışma mantığı "ebeveyn çıktıktan sonra en iyi çabayı temizlemek ve sonra çıkmak". Ancak, diğer platformlardan gerçekten emin değilim. C snippet'i Linux ve FreeBSD üzerinde çalışıyor ancak bildiğim tek şey bu ... Ayrıca, ebeveynin tekrar çatallanması veya gerçekten çıkmadan önce fd'den vazgeçmesi gibi dikkatli olmanız gereken durumlar da var (böylece bir zaman penceresi oluşturuyor) yarış kondisyonu).
Cong Ma

@Aktau - Bu tamamen güvenilir olacak.
Şeye Dayalı

1

Altında POSIX'e , exit(), _exit()ve _Exit()işlevleri için tanımlanmıştır:

  • İşlem bir kontrol işlemi ise, SIGHUP sinyali çağıran işleme ait kontrol terminalinin ön plan işlem grubundaki her bir işleme gönderilecektir.

Bu nedenle, üst işlemin kendi işlem grubu için bir kontrol işlemi olmasını ayarlarsanız, üst öğeden çıkıldığında çocuk bir SIGHUP sinyali almalıdır. Ebeveyn çöktüğünde bunun olacağından emin değilim, ama bence öyle. Kesinlikle, çarpışmayan durumlar için, iyi çalışmalıdır.

Resmin tamamını elde etmek için Temel Tanımlar (Tanımlar) bölümü ve Sistem Hizmetleri bilgilerinin exit()ve setsid()ve setpgrp()- dahil olmak üzere çok sayıda iyi baskı yapmanız gerekebilir . (Ben de!)


3
Hmm. Dokümantasyon bu konuda belirsiz ve çelişkilidir, ancak üst sürecin sadece süreç grubu için değil, oturumun baş süreci olması gerektiği görülmektedir. Seansın öncü süreci her zaman girişti ve yeni bir seansın liderlik sürecini ele geçirme sürecimi şu anda yeteneklerimin ötesine taşıyordum.
Schof

2
SIGHUP etkin bir şekilde alt süreçlere yalnızca çıkış işlemi bir giriş kabuğu ise gönderilir. opengroup.org/onlinepubs/009695399/functions/exit.html "Bir sürecin sonlandırılması doğrudan çocuklarını sonlandırmaz. Aşağıda açıklandığı gibi bir SIGHUP sinyalinin gönderilmesi dolaylı olarak çocukları / bazı durumlarda / sonlandırır /."
Rob K

1
@Rob: doğru - verdiğim teklif de bunu söylüyor: sadece bazı durumlarda çocuk süreci bir SIGHUP alıyor. Ve bunun en yaygın durum olmasına rağmen, sadece SIGHUP gönderen bir giriş kabuğu olduğunu söylemek aşırı basitleştirmedir. Birden fazla çocuklu bir süreç kendini ve çocukları için kontrol süreci olarak ayarlarsa, ÖRNEK (uygun olarak) efendi öldüğünde çocuklarına gönderilecektir. OTOH, süreçler nadiren bu kadar sıkıntıya giriyor - bu yüzden gerçekten önemli bir tartışmayı yükseltmekten daha fazla nit toplama yapıyorum.
Jonathan Leffler

2
Birkaç saat boyunca onunla dalga geçtim ve işe yaramadım. Bazı çocuklarla birlikte ebeveynlerden çıktığında ölmesi gereken bir daemonun olduğu bir davayı güzelce ele alacaktı.
Rob K

1

Örneğin pid 0'a bir sinyal gönderirseniz,

kill(0, 2); /* SIGINT */

bu sinyal tüm süreç grubuna gönderilir, böylece çocuğu etkili bir şekilde öldürür.

Aşağıdaki gibi bir şeyle kolayca test edebilirsiniz:

(cat && kill 0) | python

Daha sonra ^ D tuşuna basarsanız, metni "Terminated", stdin kapatıldığı için çıkılmak yerine Python yorumlayıcısının gerçekten öldürüldüğünün bir göstergesi olarak görürsünüz .


1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" mutlulukla Sonlandırıldı yerine 4 yazdırır
Kamil Szot

1

Başkalarıyla ilgili olduğu takdirde, C ++ 'dan çatallı alt süreçlerde JVM örneklerini ortaya çıkardığımda, ana işlem tamamlandıktan sonra JVM örneklerinin düzgün bir şekilde sonlandırılmasını sağlamanın tek yolu aşağıdakileri yapmaktı. Umarım birisi bunu yapmanın en iyi yolu değilse yorumlarda geri bildirim sağlayabilir.

1) prctl(PR_SET_PDEATHSIG, SIGHUP)Java uygulamasını başlatmadan önce çatallı çocuk işlemini çağırın execvve

2) Üst PID'si 1'e eşit olana kadar Java uygulamasına bir kapatma kancası ekleyin, ardından bir sabitleme yapın Runtime.getRuntime().halt(0). Yoklama, pskomutu çalıştıran ayrı bir kabuk başlatılarak yapılır (Bkz: PID'imi Java veya Java'da JRuby'de nasıl bulabilirim? ).

EDIT 130118:

Görünüşe göre bu sağlam bir çözüm değildi. Hala neler olup bittiğinin nüanslarını anlamak için biraz uğraşıyorum, ancak bazen bu uygulamaları ekran / SSH oturumlarında çalıştırırken hala yetim JVM süreçleri alıyordum.

Java uygulamasında PPID için anket yapmak yerine, kapatma kancasının yukarıdaki gibi temizlemeyi ve ardından sert bir duruşu gerçekleştirmesini sağladım. Sonra waitpidher şeyi sonlandırma zamanı geldiğinde spawned çocuk süreci C ++ ebeveyn app çağırmak için emin yaptı . Çocuk süreci sona erdiğinden emin olduğu için bu daha sağlam bir çözüm gibi gözükürken, ebeveyn çocuklarının sona ermesini sağlamak için mevcut referansları kullanır. Bunu, ebeveyn süreci her ne zaman isterse sona erdiren ve çocukların sona ermeden önce yetim kalıp kalmadığını anlamaya çalışan önceki çözümle karşılaştırın.


1
PID equals 1Bekleme geçerli değil. Yeni ebeveyn başka bir PID olabilir. Orijinal üst öğeden (çatal () öncesi) (getpid ()) yeni alt öğeye (çocuk (getppid ()) çataldan () önce çağrıldığında getpid () ile eşit olmadığını kontrol etmelisiniz.
Alexis Wilke

1

Linux'a özgü olan bunu yapmanın başka bir yolu, üst öğenin yeni bir PID ad alanında oluşturulmasını sağlamaktır. Daha sonra bu isim alanında PID 1 olacak ve çıktığı zaman tüm çocukları derhal öldürülecek SIGKILL.

Ne yazık ki, yeni bir PID ad alanı oluşturmak için sahip olmalısınız CAP_SYS_ADMIN. Ancak, bu yöntem çok etkilidir ve ebeveynin veya ebeveynin ilk lansmanının ötesindeki çocuklarda gerçek bir değişiklik gerektirmez.

Bkz. Klon (2) , pid_namespaces (7) ve paylaşımsız (2) .


Başka bir şekilde düzenlemem gerekiyor. Bir işlemi tüm çocuklar ve torunlar ve büyük torunlar, vb. İçin başlatma işlemi olarak yapmak için prctl kullanmak mümkündür ...
şeye kadir

0

Ebeveyn ölürse, yetimlerin PPID'si 1 olarak değişir - yalnızca kendi PPID'nizi kontrol etmeniz gerekir. Bir bakıma, bu yukarıda bahsedilen yoklamadır. İşte bunun için kabuk parçası:

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

0

Her ikisi de mükemmel olmayan 2 çözüm buldum.

1. SIGTERM sinyali alındığında tüm çocukları öldürerek öldürün (-pid).
Açıkçası, bu çözüm "kill -9" işleyemez, ancak çoğu durumda işe yarar ve çok basit çünkü tüm çocuk süreçlerini hatırlamak gerekmez.


    var childProc = require('child_process').spawn('tail', ['-f', '/dev/null'], {stdio:'ignore'});

    var counter=0;
    setInterval(function(){
      console.log('c  '+(++counter));
    },1000);

    if (process.platform.slice(0,3) != 'win') {
      function killMeAndChildren() {
        /*
        * On Linux/Unix(Include Mac OS X), kill (-pid) will kill process group, usually
        * the process itself and children.
        * On Windows, an JOB object has been applied to current process and children,
        * so all children will be terminated if current process dies by anyway.
        */
        console.log('kill process group');
        process.kill(-process.pid, 'SIGKILL');
      }

      /*
      * When you use "kill pid_of_this_process", this callback will be called
      */
      process.on('SIGTERM', function(err){
        console.log('SIGTERM');
        killMeAndChildren();
      });
    }

Aynı şekilde, process.exit'i bir yere çağırırsanız, yukarıdaki gibi 'exit' işleyicisini yükleyebilirsiniz. Not: Ctrl + C ve ani çökme, işlem grubunu öldürmek için işletim sistemi tarafından otomatik olarak işlendi, bu yüzden burada daha fazla yok.

2.Use chjj / pty.js bağlı uç kontrol ederek için yumurtlamaya süreç.
Mevcut işlemi zaten -9'u öldürdüğünüzde öldürdüğünüzde, tüm alt süreçler de otomatik olarak öldürülecektir (OS tarafından?). Sanırım mevcut süreç terminalin başka bir tarafını tutuyor, bu nedenle eğer mevcut süreç ölürse, alt süreç SIGPIPE'yi alır ve böylece ölür.


    var pty = require('pty.js');

    //var term =
    pty.spawn('any_child_process', [/*any arguments*/], {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: process.cwd(),
      env: process.env
    });
    /*optionally you can install data handler
    term.on('data', function(data) {
      process.stdout.write(data);
    });
    term.write(.....);
    */

0

Terminal kontrolünü ve oturumlarını kötüye kullanarak 3 işlemle taşınabilir, sorgulama yapmayan bir çözüm yapmayı başardım. Bu zihinsel mastürbasyon, ama işe yarıyor.

Hile:

  • A süreci başlatıldı
  • A işlemi bir P borusu oluşturur (ve asla ondan okumaz)
  • A işlem çatalları B işlemine
  • B işlemi yeni bir oturum oluşturur
  • B işlemi bu yeni oturum için sanal bir terminal ayırır
  • B prosesi, SIGCHLD işleyicisini çocuk çıkarken ölmek üzere kurar
  • B işlemi bir SIGPIPE işleyicisi ayarlar
  • işlem B çatalları işlem C'ye
  • C işlemi ihtiyaç duyduğu her şeyi yapar (örneğin, exec () değiştirilmemiş ikiliyi çalıştırır veya her türlü mantığı çalıştırır)
  • B işlemi P borusuna yazar (ve bu şekilde engeller)
  • İşlem Bekleme () işlemi B işleminde yapılır ve öldüğünde çıkar

Bu şekilde:

  • A süreci ölürse: B süreci SIGPIPE alır ve ölür
  • eğer işlem B ölürse: A işleminin wait () değeri geri döner ve ölürse, C işlemi bir SIGHUP alır (çünkü terminal bağlı bir oturumun oturum lideri öldüğünde, ön plan işlem grubundaki tüm işlemler bir SIGHUP alır)
  • eğer C süreci ölürse: B süreci SIGCHLD alır ve ölür, dolayısıyla A süreci ölür

eksiklikleri:

  • C işlemi SIGHUP ile baş edemiyor
  • C işlemi farklı bir oturumda çalıştırılacak
  • C süreci oturum / süreç grubu API'sını kullanamaz çünkü kırılgan kurulumu bozar
  • bu tür her işlem için bir terminal oluşturmak şimdiye kadarki en iyi fikir değil

0

7 yıl geçmesine rağmen, geliştirme sırasında webpack-dev-server'ı başlatması gereken ve arka uç işlemi durduğunda onu öldürmesi gereken SpringBoot uygulamasını çalıştırdığım için bu sorunla karşılaştım.

Kullanmaya çalışıyorum Runtime.getRuntime().addShutdownHookama Windows 10'da çalıştı, ancak Windows 7'de çalıştı.

İşlemin sonlandırılmasını bekleyen veya InterruptedExceptionher iki Windows sürümünde düzgün çalıştığı görünen özel bir iş parçacığı kullanmak için değiştirdim .

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

0

Tarihsel olarak, UNIX v7'den itibaren işlem sistemi, bir sürecin ana kimliğini kontrol ederek süreçlerin yetimliğini tespit etmiştir. Dediğim gibi, tarihsel olarak, init(8)sistem süreci tek bir nedenden dolayı özel bir süreçtir: Ölemez. Ölemez çünkü yeni bir üst işlem kimliği atamak için çekirdek algoritması bu gerçeğe bağlıdır. bir süreç exit(2)çağrısını yürüttüğünde (bir işlem sistemi çağrısı veya bir sinyal veya benzeri göndererek harici görev yoluyla), çekirdek bu işlemin tüm alt öğelerini init işleminin kimliğini üst süreç kimliği olarak yeniden atar. Bu, bir sürecin yetim olup olmadığını bilmenin en kolay testine ve en taşınabilir yoluna yol açar. Sadece getppid(2)sistem çağrısının sonucunu kontrol edin ve sistem çağrısının işlem kimliği olup olmadığını kontrol edin .init(2) sonra sistem çağrısından önce yetim kalmıştır.

Bu yaklaşımdan sorunlara yol açabilecek iki konu ortaya çıkıyor:

  • ilk olarak, initişlemi herhangi bir kullanıcı işlemine değiştirme olanağına sahibiz , bu nedenle init işleminin her zaman tüm yetim süreçlerin üst öğesi olmasını nasıl sağlayabiliriz? Eh, içinde exitsistem çağrı kodu çağrısı yürütme işlemi init süreci (pid eşit 1'e ile süreci) olup olmadığını görmek için bir açık çek var ve bu durum buysa, çekirdek panikler (O korumak artık mümkün olmamalıdır işlem hiyerarşisi), bu nedenle init işleminin exit(2)çağrı yapmasına izin verilmez .
  • ikincisi, yukarıda açıklanan temel testte bir yarış durumu vardır. Init process 'id'in tarihsel olarak olduğu varsayılır 1, ancak bu POSIX yaklaşımı tarafından garanti edilmez, (diğer yanıtta açıklandığı gibi) yalnızca bir sistemin proses kimliğinin bu amaç için ayrıldığını belirtir. Hemen hemen hiçbir posix uygulaması bunu yapmaz ve orijinal unix'ten türetilen sistemlerde 1, getppid(2)sistem çağrısına yanıt olarak sürecin yetim olduğunu varsaymak için yeterli olduğunu varsayabilirsiniz. Kontrol etmenin başka bir yolu getppid(2)da çataldan hemen sonra yapmak ve bu değeri yeni bir çağrının sonucu ile karşılaştırmaktır. Her iki çağrı birlikte atomik olmadığından ve ana süreç fork(2)ilk getppid(2)sistem çağrısından önce ve sonra ölebildiğinden, bu her durumda işe yaramaz . Proses parent id only changes once, when its parent does ançıkışı (2) call, so this should be enough to check if thegetppid (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 ofinit (8) `, ancak bu süreçlerin de ebeveyni olmadığını güvenli bir şekilde kabul edebilirsiniz (init işlemini bir sistemde değiştirdiğiniz durumlar hariç)

-1

Çocuğa ortam kullanarak ebeveyn pid'i geçtim, sonra çocuktan / proc / $ ppid olup olmadığını düzenli olarak kontrol ettim.

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.