Bagaimana cara membuat proses anak mati setelah orang tua keluar?

209

Misalkan saya memiliki proses yang memunculkan satu proses anak. Sekarang ketika proses orangtua keluar karena alasan apa pun (biasanya atau tidak normal, dengan membunuh, ^ C, menyatakan kegagalan atau apa pun), saya ingin proses anak mati. Bagaimana cara melakukannya dengan benar?


Beberapa pertanyaan serupa tentang stackoverflow:


Beberapa pertanyaan serupa tentang stackoverflow untuk Windows :

Paweł Hajdan
sumber

Jawaban:

189

Child dapat meminta kernel untuk mengirimkan SIGHUP(atau sinyal lain) ketika orang tua meninggal dengan menentukan opsi PR_SET_PDEATHSIGdi prctl()syscall seperti ini:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Lihat man 2 prctldetailnya.

Sunting: Ini hanya Linux

qrdl
sumber
5
Ini adalah solusi yang buruk karena orang tua mungkin sudah meninggal. Kondisi balapan. Solusi yang benar: stackoverflow.com/a/17589555/412080
Maxim Egorushkin
16
Memanggil jawaban yang buruk tidak terlalu baik - bahkan jika itu tidak membahas kondisi ras. Lihat jawaban saya tentang cara menggunakan prctl()dalam kondisi bebas lomba. Btw, jawaban yang ditautkan oleh Maxim salah.
maxschlepzig
4
Ini hanya anser yang salah. Ini akan mengirimkan sinyal ke proses anak pada saat utas yang disebut garpu mati, bukan saat proses induk mati.
Lothar
2
@Lothar Akan menyenangkan untuk melihat semacam bukti. man prctlmengatakan: Atur orang tua memproses sinyal kematian dari proses panggilan ke arg2 (baik nilai sinyal dalam kisaran 1..maxsig, atau 0 untuk menghapus). Ini adalah sinyal bahwa proses panggilan akan didapat ketika orang tuanya meninggal. Nilai ini dihapus untuk anak dari fork (2) dan (sejak Linux 2.4.36 / 2.6.23) ketika mengeksekusi biner set-user-ID atau set-group-ID.
qrdl
1
@maxschlepzig Terima kasih atas tautan baru. Sepertinya tautan sebelumnya tidak valid. Ngomong-ngomong, setelah bertahun-tahun, masih belum ada api untuk mengatur opsi di sisi induk. Sayang sekali.
rox
68

Saya mencoba untuk memecahkan masalah yang sama, dan karena program saya harus berjalan pada OS X, solusi hanya Linux tidak bekerja untuk saya.

Saya sampai pada kesimpulan yang sama dengan orang lain di halaman ini - tidak ada cara yang kompatibel dengan POSIX untuk memberi tahu seorang anak ketika orang tua meninggal. Jadi saya membereskan hal terbaik berikutnya - mengadakan pemilihan anak.

Ketika proses orang tua meninggal (karena alasan apa pun) proses orang tua anak menjadi proses 1. Jika anak hanya melakukan jajak pendapat secara berkala, ia dapat memeriksa apakah orang tuanya adalah 1. Jika demikian, anak tersebut harus keluar.

Ini tidak bagus, tetapi berfungsi, dan lebih mudah daripada solusi polling TCP socket / lockfile yang disarankan di tempat lain di halaman ini.

Schof
sumber
6
Solusi yang sangat baik. Terus memanggil getppid () hingga mengembalikan 1 dan kemudian keluar. Ini bagus dan saya sekarang menggunakannya juga. Solusi non-pollig akan lebih baik. Terima kasih, Schof.
neoneye
10
Sekadar info, pada Solaris jika Anda berada di zona, angka gettpid()tidak menjadi 1 tetapi mendapatkan pidpenjadwal zona (proses zsched).
Patrick Schlüter
4
Jika ada yang bertanya-tanya, dalam sistem Android pid tampaknya menjadi 0 (proses Sistem pid) bukan 1, ketika orang tua meninggal.
Rui Marques
2
Untuk memiliki cara yang lebih kuat dan independen dari platform, sebelum melakukan fork (), cukup getpid () dan jika getppid () dari child berbeda, keluar.
Sebastien
2
Ini tidak berfungsi jika Anda tidak mengontrol proses anak. Misalnya, saya sedang mengerjakan perintah yang membungkus find (1), dan saya ingin memastikan find tersebut terbunuh jika wrappernya mati karena suatu alasan.
Lucretiel
34

Saya telah mencapai ini di masa lalu dengan menjalankan kode "asli" di "child" dan kode "spawned" di "parent" (yaitu: Anda membalikkan arti tes yang biasa setelah fork()). Kemudian jebak SIGCHLD dalam kode "spawned" ...

Mungkin tidak mungkin dalam kasus Anda, tetapi lucu ketika berfungsi.

dmckee --- mantan kucing moderator
sumber
Solusi yang sangat bagus, terima kasih! Yang diterima saat ini lebih umum, tetapi milik Anda lebih portabel.
Paweł Hajdan
1
Masalah besar dengan melakukan pekerjaan di induk adalah Anda mengubah proses induk. Dalam hal server yang harus menjalankan "selamanya", itu bukan opsi.
Alexis Wilke
29

Jika Anda tidak dapat mengubah proses anak, Anda dapat mencoba sesuatu seperti berikut:

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 */

Ini menjalankan anak dari dalam proses shell dengan kontrol pekerjaan diaktifkan. Proses anak lahir di latar belakang. Shell menunggu baris baru (atau EOF) lalu membunuh anak itu.

Ketika orang tua meninggal - tidak peduli apa alasannya - itu akan menutup ujung pipa. Cangkang anak akan mendapatkan EOF dari bacaan dan melanjutkan untuk membunuh proses anak yang berlatar belakang.

Phil Rutschman
sumber
2
Bagus, tapi lima panggilan sistem, dan dia menelurkan sepuluh baris kode membuat saya agak skeptis dengan kinerja kode ini.
Oleiade
+1. Anda dapat menghindari dup2dan mengambil alih stdin dengan menggunakan read -uflag untuk membaca dari deskriptor file tertentu. Saya juga menambahkan setpgid(0, 0)anak untuk mencegahnya keluar saat menekan ^ C di terminal.
Greg Hewgill
Urutan argumen dup2()panggilan itu salah. Jika Anda ingin menggunakan pipes[0]sebagai stdin Anda harus menulis dup2(pipes[0], 0)alih-alih dup2(0, pipes[0]). Di dup2(oldfd, newfd)sinilah panggilan menutup newfd yang sebelumnya terbuka.
maxschlepzig
@Oleiade, saya setuju, terutama karena sh melahirkan hanya garpu lain untuk melaksanakan proses anak nyata ...
maxschlepzig
16

Di Linux, Anda dapat memasang sinyal kematian orang tua pada anak, misalnya:

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

Perhatikan bahwa menyimpan id proses induk sebelum garpu dan mengujinya pada anak setelah prctl()menghilangkan kondisi ras antara prctl()dan keluar dari proses yang disebut anak.

Perhatikan juga bahwa sinyal kematian orang tua dari anak tersebut dihapus pada anak-anak yang baru dibuat sendiri. Itu tidak terpengaruh oleh execve().

Tes itu dapat disederhanakan jika kita yakin bahwa proses sistem yang bertugas mengadopsi semua anak yatim memiliki PID 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 ...

Mengandalkan proses sistem yang sedang initdan memiliki PID 1 tidak mudah dibawa. POSIX.1-2008 menetapkan :

ID proses induk dari semua proses anak yang ada dan proses zombie dari proses pemanggilan harus diatur ke ID proses dari proses sistem yang ditentukan implementasi. Artinya, proses ini harus diwarisi oleh proses sistem khusus.

Secara tradisional, proses sistem yang mengadopsi semua anak yatim adalah PID 1, yaitu init - yang merupakan nenek moyang dari semua proses.

Pada sistem modern seperti Linux atau FreeBSD proses lain mungkin memiliki peran itu. Misalnya, di Linux, suatu proses dapat memanggil prctl(PR_SET_CHILD_SUBREAPER, 1)untuk memantapkan dirinya sebagai proses sistem yang mewarisi semua anak yatim dari keturunannya (lih. Contoh pada Fedora 25).

maxschlepzig
sumber
Saya tidak mengerti "Tes itu dapat disederhanakan jika kita yakin bahwa kakek nenek selalu proses init". Ketika proses orang tua meninggal, suatu proses menjadi anak dari proses init (pid 1), bukan anak dari kakek-nenek, kan? Jadi tes sepertinya selalu benar.
Johannes Schaub - litb
1
@ JohannesSchaub-litb, tidak harus PID 1 - POSIX menentukan: ID proses induk dari semua proses anak yang ada dan proses zombie dari proses pemanggilan harus diatur ke ID proses dari proses sistem yang ditentukan-implementasi . Artinya, proses ini harus diwarisi oleh proses sistem khusus. Sebagai contoh, ketika berjalan pada sistem Fedora 25 di terminal Gnome, proses sistem khusus memiliki PID! = 1: gist.github.com/gsauthof/8c8406748e536887c45ec14b2e476cbc
maxschlepzig
menarik, terima kasih. meskipun, saya gagal melihat apa hubungannya dengan kakek-nenek.
Johannes Schaub - litb
1
@ JohannesSchaub-litb, Anda tidak selalu dapat berasumsi bahwa nenek moyang dari suatu proses akan init(8)diproses .... satu-satunya hal yang dapat Anda asumsikan adalah bahwa ketika proses induk mati, adalah bahwa id induknya akan berubah. Ini sebenarnya terjadi sekali dalam kehidupan suatu proses .... dan adalah ketika proses 'orang tua meninggal. Hanya ada satu pengecualian utama untuk ini, dan untuk init(8)anak - anak, tetapi Anda terlindungi dari ini, karena init(8)tidak pernah exit(2)(panik dalam hal ini)
Luis Colorado
1
Sayangnya, jika seorang anak memotong dari utas, dan kemudian utas keluar, proses anak tersebut akan mendapatkan SIGTERM.
rox
14

Demi kelengkapan. Di macOS Anda dapat menggunakan 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);
}
neoneye
sumber
Anda dapat melakukan ini dengan API yang sedikit lebih bagus, menggunakan sumber pengiriman dengan DISPATCH_SOURCE_PROC dan PROC_EXIT.
russbishop
Untuk alasan apa pun, ini menyebabkan Mac saya panik. Menjalankan proses dengan kode ini memiliki peluang 50% atau lebih dari itu membeku, menyebabkan para penggemar berputar pada tingkat yang belum pernah saya dengar sebelumnya (super cepat), dan kemudian mac mati begitu saja. SANGAT HATI-HATI DENGAN KODE INI .
Qix - MONICA DISEBUTKAN
Sepertinya di macOS saya, proses anak keluar secara otomatis setelah orang tua keluar. Saya tidak tahu kenapa.
Yi Lin Liu
@YiLinLiu iirc Saya menggunakan NSTaskatau memunculkan posix . Lihat startTaskfungsi dalam kode saya di sini: github.com/neoneye/newton-commander-browse/blob/master/Classes/…
neoneye
11

Apakah proses anak memiliki pipa ke / dari proses induk? Jika demikian, Anda akan menerima SIGPIPE jika menulis, atau dapatkan EOF saat membaca - kondisi ini dapat dideteksi.

MarkR
sumber
1
Saya menemukan ini tidak terjadi dengan andal, setidaknya pada OS X.
Schof
Saya datang ke sini untuk menambahkan jawaban ini. Ini jelas merupakan solusi yang saya gunakan untuk kasus ini.
Dolda2000
titik peringatan: systemd menonaktifkan SIGPIPE secara default dalam layanan yang dikelolanya, tetapi Anda masih dapat memeriksa penutupan pipa. Lihat freedesktop.org/software/systemd/man/systemd.exec.html di bawah IgnoreSIGPIPE
jdizzle
11

Terinspirasi oleh jawaban lain di sini, saya datang dengan solusi all-POSIX berikut. Gagasan umum adalah menciptakan proses peralihan antara orangtua dan anak, yang memiliki satu tujuan: Perhatikan ketika orangtua meninggal, dan secara eksplisit membunuh anak itu.

Jenis solusi ini berguna ketika kode pada anak tidak dapat dimodifikasi.

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

Ada dua peringatan kecil dengan metode ini:

  • Jika Anda dengan sengaja membunuh proses perantara, maka anak tidak akan terbunuh ketika orang tua meninggal.
  • Jika anak keluar sebelum orang tua, maka proses perantara akan mencoba untuk membunuh pid anak asli, yang sekarang bisa merujuk ke proses yang berbeda. (Ini bisa diperbaiki dengan lebih banyak kode dalam proses perantara.)

Selain itu, kode aktual yang saya gunakan adalah dalam Python. Ini dia untuk kelengkapan:

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)
Greg Hewgill
sumber
Perhatikan bahwa beberapa waktu yang lalu, di bawah IRIX, saya menggunakan skema induk / anak di mana saya memiliki pipa antara keduanya dan membaca dari pipa menghasilkan SIGHUP jika salah satu meninggal. Itulah cara saya biasa membunuh anak-anak garpu saya (), tanpa perlu proses peralihan.
Alexis Wilke
Saya pikir peringatan kedua Anda salah. Pid seorang anak adalah sumber daya milik orang tuanya dan tidak dapat dibebaskan / digunakan kembali sampai orang tuanya (proses perantara) menunggu di atasnya (atau mengakhiri dan membiarkannya menunggu di atasnya).
R .. GitHub BERHENTI MEMBANTU ICE
7

Saya tidak percaya itu mungkin untuk menjamin bahwa hanya menggunakan panggilan POSIX standar. Seperti kehidupan nyata, begitu seorang anak dilahirkan, ia memiliki kehidupannya sendiri.

Hal ini dimungkinkan untuk proses induk untuk menangkap yang paling mungkin peristiwa pemutusan, dan berusaha untuk membunuh proses anak pada saat itu, tapi selalu ada beberapa yang tidak bisa ditangkap.

Misalnya, tidak ada proses yang dapat menangkap a SIGKILL. Ketika kernel menangani sinyal ini, ia akan mematikan proses yang ditentukan tanpa pemberitahuan apa pun terhadap proses itu.

Untuk memperluas analogi - satu-satunya cara standar lain untuk melakukannya adalah bagi anak untuk bunuh diri ketika menemukan bahwa ia tidak lagi memiliki orangtua.

Ada cara khusus untuk melakukannya dengan Linux prctl(2)- lihat jawaban lain.

Alnitak
sumber
6

Seperti yang ditunjukkan oleh orang lain, mengandalkan pid orang tua menjadi 1 ketika orang tua keluar adalah non-portabel. Alih-alih menunggu ID proses induk tertentu, tunggulah hingga ID berubah:

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

Tambahkan tidur mikro seperti yang diinginkan jika Anda tidak ingin polling dengan kecepatan penuh.

Pilihan ini tampaknya lebih sederhana bagi saya daripada menggunakan pipa atau mengandalkan sinyal.

pengguna2168915
sumber
Sayangnya, solusi itu tidak kuat. Bagaimana jika proses induk mati sebelum Anda mendapatkan nilai awal? Anak itu tidak akan pernah keluar.
dgatwood
@ Dodwood, apa maksudmu?!? Yang pertama getpid()dilakukan pada orang tua sebelum menelepon fork(). Jika orangtua meninggal sebelum itu, anak itu tidak ada. Apa yang mungkin terjadi adalah anak keluar hidup dengan orang tua untuk sementara waktu.
Alexis Wilke
Dalam contoh yang agak dibuat-buat ini, ia berfungsi, tetapi dalam kode dunia nyata, garpu hampir selalu diikuti oleh exec, dan proses baru harus memulai kembali dengan meminta PPID-nya. Di antara dua cek itu, jika orangtua pergi, anak itu tidak tahu. Selain itu, Anda tidak mungkin memiliki kontrol atas kode induk dan kode anak (atau Anda bisa meneruskan PPID sebagai argumen). Jadi sebagai solusi umum, pendekatan itu tidak bekerja dengan baik. Dan secara realistis, jika OS mirip UNIX keluar tanpa menjadi 1, begitu banyak hal akan pecah sehingga saya tidak bisa membayangkan ada orang yang melakukannya.
dgatwood
pass parent pid adalah argumen baris perintah ketika melakukan exec untuk anak.
Nish
2
Polling dengan kecepatan penuh itu gila.
maxschlepzig
4

Pasang penjebak perangkap untuk menangkap SIGINT, yang membunuh proses anak Anda jika masih hidup, meskipun poster lain benar bahwa itu tidak akan menangkap SIGKILL.

Buka file .lockfile dengan akses eksklusif dan minta polling anak mencoba membukanya - jika terbuka, proses anak harus keluar

Ana Betts
sumber
Atau, anak itu dapat membuka lockfile dalam utas terpisah, dalam mode blocking, dalam hal ini ini bisa menjadi solusi yang cukup bagus dan bersih. Mungkin memiliki beberapa batasan portabilitas.
Jean
4

Solusi ini bekerja untuk saya:

  • Berikan pipa stdin ke anak - Anda tidak harus menulis data apa pun ke dalam aliran.
  • Anak membaca tanpa batas dari stdin hingga EOF. EOF memberi sinyal bahwa orang tua telah pergi.
  • Ini adalah cara mudah dan portabel untuk mendeteksi ketika orang tua telah pergi. Bahkan jika orangtua crash, OS akan menutup pipa.

Ini untuk proses tipe pekerja yang keberadaannya hanya masuk akal ketika orang tua masih hidup.

joonas.fi
sumber
@SebastianJylanki Saya tidak ingat jika saya mencoba, tetapi mungkin berhasil karena primitif (aliran POSIX) cukup standar di seluruh OS.
joonas.fi
3

Saya pikir cara cepat dan kotor adalah membuat pipa antara anak dan orang tua. Ketika orang tua keluar, anak-anak akan menerima SIGPIPE.

Yefei
sumber
SIGPIPE tidak dikirim pada tutup pipa, itu hanya dikirim ketika anak mencoba menulis untuk itu.
Alcaro
3

Beberapa poster telah menyebutkan pipa dan kqueue. Bahkan Anda juga dapat membuat sepasang soket domain Unix yang terhubung melalui socketpair()panggilan. Jenis soket seharusnya SOCK_STREAM.

Biarkan kami mengira Anda memiliki dua deskriptor file socket fd1, fd2. Sekarang fork()untuk membuat proses anak, yang akan mewarisi fds. Pada orang tua Anda menutup fd2 dan pada anak Anda menutup fd1. Sekarang setiap proses dapat poll()membuka fd yang tersisa pada akhir POLLINacara. Selama masing-masing pihak tidak secara eksplisit close()menemukan selama masa normal, Anda dapat cukup yakin bahwa sebuah POLLHUPbendera harus mengindikasikan pemutusan pihak lain (tidak masalah bersih atau tidak). Setelah diberitahu tentang peristiwa ini, anak dapat memutuskan apa yang harus dilakukan (misalnya mati).

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

Anda dapat mencoba mengkompilasi kode proof-of-concept di atas, dan menjalankannya di terminal seperti ./a.out &. Anda memiliki sekitar 100 detik untuk bereksperimen dengan membunuh PID induk dengan berbagai sinyal, atau itu hanya akan keluar. Dalam kedua kasus, Anda akan melihat pesan "child: parent hung up".

Dibandingkan dengan metode yang menggunakan SIGPIPEhandler, metode ini tidak perlu mencoba write()panggilan.

Metode ini juga simetris , yaitu proses dapat menggunakan saluran yang sama untuk memantau keberadaan satu sama lain.

Solusi ini hanya memanggil fungsi POSIX. Saya mencoba ini di Linux dan FreeBSD. Saya pikir itu harus bekerja pada Unix lain tetapi saya belum benar-benar diuji

Lihat juga:

  • unix(7)Linux halaman manual, unix(4)untuk FreeBSD, poll(2), socketpair(2), socket(7)di Linux.
Cong Ma
sumber
Sangat keren, saya benar-benar bertanya-tanya apakah ini memiliki masalah keandalan sekalipun. Sudahkah Anda menguji ini dalam produksi? Dengan berbagai aplikasi?
Aktau
@ Aktau, saya telah menggunakan Python yang setara dengan trik ini dalam program Linux. Saya membutuhkannya karena logika kerja anak adalah "melakukan upaya terbaik setelah orangtua keluar dan kemudian keluar juga". Namun, saya benar-benar tidak yakin tentang platform lain. Cuplikan C berfungsi di Linux dan FreeBSD, tetapi hanya itu yang saya tahu ... Juga, ada beberapa kasus ketika Anda harus berhati-hati, seperti induk forking lagi, atau orang tua menyerahkan fd sebelum benar-benar keluar (sehingga menciptakan jendela waktu untuk kondisi balapan).
Cong Ma 9'13
@ Aktau - Ini akan sepenuhnya dapat diandalkan.
Mahakuasa
1

Di bawah POSIX , yang exit(), _exit()dan _Exit()fungsi didefinisikan untuk:

  • Jika proses tersebut merupakan proses pengendalian, sinyal SIGHUP harus dikirim ke setiap proses dalam kelompok proses latar depan terminal pengendali yang termasuk dalam proses pemanggilan.

Jadi, jika Anda mengatur agar proses induk menjadi proses kontrol untuk grup prosesnya, anak harus mendapatkan sinyal SIGHUP saat orangtua keluar. Saya tidak benar-benar yakin itu terjadi ketika orang tua crash, tapi saya pikir itu terjadi. Tentu saja, untuk kasus-kasus non-crash, itu akan berfungsi dengan baik.

Perhatikan bahwa Anda mungkin harus membaca banyak cetak kecil - termasuk bagian Definisi Dasar (Definisi), serta informasi Layanan Sistem untuk exit()dan setsid()dan setpgrp()- untuk mendapatkan gambaran lengkap. (Begitu juga aku!)

Jonathan Leffler
sumber
3
Hmm. Dokumentasinya tidak jelas dan kontradiktif mengenai hal ini, tetapi tampaknya proses induk harus menjadi proses utama untuk sesi, bukan hanya grup proses. Proses utama untuk sesi selalu masuk, dan proses saya untuk mengambil alih karena proses utama untuk sesi baru berada di luar kemampuan saya saat ini.
Schof
2
SIGHUP secara efektif hanya akan dikirim ke proses anak jika proses yang keluar adalah shell login. opengroup.org/onlinepubs/009695399/functions/exit.html "Pengakhiran suatu proses tidak secara langsung menghentikan anak-anaknya. Pengiriman sinyal SIGHUP seperti yang dijelaskan di bawah ini secara tidak langsung mengakhiri anak-anak / dalam beberapa keadaan /."
Rob K
1
@Rob: benar - itulah yang kutipan saya katakan, juga: bahwa hanya dalam beberapa keadaan proses anak mendapatkan SIGHUP. Dan ini adalah penyederhanaan berlebihan untuk mengatakan bahwa itu hanyalah shell login yang mengirim SIGHUP, meskipun itu adalah kasus yang paling umum. Jika suatu proses dengan beberapa anak menetapkan dirinya sebagai proses pengendalian untuk dirinya sendiri dan anak-anaknya, maka SIGHUP akan (dengan mudah) dikirim kepada anak-anaknya ketika sang master meninggal. OTOH, proses jarang mengalami masalah sebanyak itu - jadi saya lebih memilih daripada memunculkan pertengkaran yang sangat signifikan.
Jonathan Leffler
2
Saya bermain-main dengan itu selama beberapa jam dan tidak bisa membuatnya bekerja. Itu akan dengan baik menangani kasus di mana saya memiliki dasmon dengan beberapa anak yang semuanya harus mati ketika orang tua keluar.
Rob K
1

Jika Anda mengirim sinyal ke pid 0, gunakan misalnya

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

sinyal itu dikirim ke seluruh kelompok proses, sehingga secara efektif membunuh anak.

Anda dapat mengujinya dengan mudah seperti:

(cat && kill 0) | python

Jika Anda menekan ^ D, Anda akan melihat teks "Terminated"sebagai indikasi bahwa penerjemah Python memang telah terbunuh, alih-alih hanya keluar karena stdin ditutup.

Thorbiörn Fritzon
sumber
1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" dengan senang hati mencetak 4 bukannya Dihentikan
Kamil Szot
1

Dalam hal ini relevan untuk orang lain, ketika saya menelurkan contoh JVM dalam proses anak bercabang dari C ++, satu-satunya cara saya bisa mendapatkan contoh JVM untuk berakhir dengan benar setelah proses induk selesai adalah untuk melakukan hal berikut. Semoga seseorang dapat memberikan umpan balik dalam komentar jika ini bukan cara terbaik untuk melakukan ini.

1) Panggil prctl(PR_SET_PDEATHSIG, SIGHUP)proses anak bercabang seperti yang disarankan sebelum meluncurkan aplikasi Java via execv, dan

2) Tambahkan hook shutdown ke aplikasi Java yang melakukan polling hingga PID induknya sama dengan 1, lalu lakukan dengan keras Runtime.getRuntime().halt(0). Polling dilakukan dengan meluncurkan shell terpisah yang menjalankan psperintah (Lihat: Bagaimana cara menemukan PID saya di Jawa atau JRuby di Linux? ).

EDIT 130118:

Tampaknya itu bukan solusi yang kuat. Saya masih berjuang sedikit untuk memahami nuansa dari apa yang terjadi, tetapi saya kadang-kadang masih mendapatkan proses JVM yatim ketika menjalankan aplikasi ini dalam sesi layar / SSH.

Alih-alih polling untuk PPID di aplikasi Java, saya hanya meminta hook shutdown melakukan pembersihan diikuti dengan penghentian keras seperti di atas. Kemudian saya memastikan untuk memohon waitpiddalam aplikasi induk C ++ pada proses anak yang dilahirkan ketika tiba saatnya untuk menghentikan semuanya. Ini tampaknya menjadi solusi yang lebih kuat, karena proses anak memastikan bahwa itu berakhir, sementara orangtua menggunakan referensi yang ada untuk memastikan bahwa anak-anaknya berakhir. Bandingkan ini dengan solusi sebelumnya yang membuat proses orangtua berakhir kapan saja, dan mintalah anak-anak mencoba mencari tahu apakah mereka telah yatim piatu sebelum diakhiri.

jasterm007
sumber
1
The PID equals 1menunggu tidak valid. Induk baru bisa berupa PID lain. Anda harus memeriksa apakah itu berubah dari induk asli (getpid () sebelum garpu ()) ke induk baru (getppid () pada anak tidak sama dengan getpid () ketika dipanggil sebelum garpu ()).
Alexis Wilke
1

Cara lain untuk melakukan ini yang spesifik Linux adalah membuat orang tua dibuat di namespace PID baru. Maka akan menjadi PID 1 di namespace itu, dan ketika keluar semua anak-anak itu akan segera dibunuh SIGKILL.

Sayangnya, untuk membuat namespace PID baru Anda harus memiliki CAP_SYS_ADMIN. Namun, metode ini sangat efektif dan tidak memerlukan perubahan nyata pada orang tua atau anak-anak di luar peluncuran awal orang tua.

Lihat clone (2) , pid_namespaces (7) , dan unshare (2) .

Beraneka ragam
sumber
Saya perlu mengedit dengan cara lain. Adalah mungkin untuk menggunakan prctl untuk membuat proses bertindak sebagai proses init untuk semua anak dan cucu, dan cucu-cucu hebat, dll ...
Mahakuasa
0

Jika orang tua meninggal, PPID anak yatim berubah menjadi 1 - Anda hanya perlu memeriksa PPID Anda sendiri. Di satu sisi, ini adalah polling, yang disebutkan di atas. di sini adalah potongan shell untuk itu:

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
alex K
sumber
0

Saya menemukan 2 solusi, keduanya tidak sempurna.

1.Bunuh semua anak dengan cara dibunuh (-pid) saat menerima sinyal SIGTERM.
Jelas, solusi ini tidak dapat menangani "kill -9", tetapi ia bekerja untuk sebagian besar kasus dan sangat sederhana karena tidak perlu mengingat semua proses anak.


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

Dengan cara yang sama, Anda dapat menginstal 'keluar' handler seperti cara di atas jika Anda memanggil process.exit di suatu tempat. Catatan: Ctrl + C dan crash tiba-tiba telah secara otomatis diproses oleh OS untuk mematikan grup proses, jadi tidak ada lagi di sini.

2.Use chjj / pty.js untuk bertelur proses Anda dengan mengendalikan terminal terpasang.
Ketika Anda membunuh proses saat ini dengan cara apapun bahkan membunuh -9, semua proses anak akan secara otomatis terbunuh juga (oleh OS?). Saya kira itu karena proses saat ini memegang sisi lain terminal, jadi jika proses saat ini mati, proses anak akan mendapatkan SIGPIPE sehingga mati.


    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(.....);
    */
osexp2003
sumber
0

Saya berhasil melakukan solusi portabel, non-polling dengan 3 proses dengan menyalahgunakan kontrol terminal dan sesi. Ini adalah masturbasi mental, tetapi berhasil.

Caranya adalah:

  • proses A dimulai
  • proses A membuat pipa P (dan tidak pernah membaca darinya)
  • memproses A garpu ke dalam proses B
  • proses B menciptakan sesi baru
  • proses B mengalokasikan terminal virtual untuk sesi baru itu
  • proses B menginstal pawang SIGCHLD untuk mati ketika anak keluar
  • proses B menetapkan penangan SIGPIPE
  • proses B bercabang menjadi proses C
  • proses C melakukan apa pun yang diperlukan (mis. exec () s biner yang tidak dimodifikasi atau menjalankan logika apa pun)
  • proses B menulis ke pipa P (dan memblok seperti itu)
  • proses tunggu () pada proses B dan keluar saat mati

Seperti itu:

  • jika proses A mati: proses B mendapat SIGPIPE dan mati
  • jika proses B mati: proses A menunggu () kembali dan mati, proses C mendapat SIGHUP (karena ketika ketua sesi sesi dengan terminal terpasang mati, semua proses dalam kelompok proses latar depan mendapat SIGHUP)
  • jika proses C mati: proses B mendapat SIGCHLD dan mati, jadi proses A mati

Kekurangan:

  • proses C tidak dapat menangani SIGHUP
  • proses C akan dijalankan dalam sesi yang berbeda
  • proses C tidak dapat menggunakan sesi / grup proses API karena akan merusak pengaturan rapuh
  • membuat terminal untuk setiap operasi semacam itu bukanlah ide terbaik yang pernah ada
Marek Dopiera
sumber
0

Meskipun 7 tahun telah berlalu, saya baru saja mengalami masalah ini karena saya menjalankan aplikasi SpringBoot yang perlu memulai webpack-dev-server selama pengembangan dan perlu membunuhnya ketika proses backend berhenti.

Saya mencoba menggunakan Runtime.getRuntime().addShutdownHooktetapi berfungsi pada Windows 10 tetapi tidak pada Windows 7.

Saya telah mengubahnya untuk menggunakan utas khusus yang menunggu proses untuk keluar atau InterruptedExceptionyang tampaknya berfungsi dengan baik pada kedua versi Windows.

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();
}
Ido Ran
sumber
0

Secara historis, dari UNIX v7, sistem proses telah mendeteksi orphanity proses dengan memeriksa id induk suatu proses. Seperti yang saya katakan, secara historis, init(8)proses sistem adalah proses khusus hanya dengan satu alasan: Tidak bisa mati. Itu tidak bisa mati karena algoritma kernel untuk berurusan dengan menetapkan proses id induk baru, tergantung pada fakta ini. ketika suatu proses mengeksekusi exit(2)panggilannya (melalui pemanggilan sistem proses atau dengan tugas eksternal sebagai mengirimkan sinyal atau sejenisnya) kernel menugaskan kembali semua anak dari proses ini id dari proses init sebagai id proses orang tua mereka. Ini mengarah pada tes yang paling mudah, dan cara yang paling portabel untuk mengetahui apakah suatu proses telah mendapatkan anak yatim. Cukup periksa hasil dari getppid(2)panggilan sistem dan jika itu adalah id proses dariinit(2) proses maka proses mendapat yatim sebelum panggilan sistem.

Dua masalah muncul dari pendekatan ini yang dapat menyebabkan masalah:

  • pertama, kami memiliki kemungkinan mengubah initproses menjadi proses pengguna apa pun, jadi Bagaimana kami dapat memastikan bahwa proses init akan selalu menjadi induk dari semua proses anak yatim? Nah, dalam exitkode panggilan sistem ada pemeriksaan eksplisit untuk melihat apakah proses mengeksekusi panggilan adalah proses init (proses dengan pid sama dengan 1) dan jika itu masalahnya, kernel panik (Seharusnya tidak bisa lagi mempertahankan hirarki proses) sehingga tidak diizinkan untuk proses init untuk melakukan exit(2)panggilan.
  • kedua, ada kondisi lomba dalam tes dasar yang diungkapkan di atas. ID proses init diasumsikan secara historis 1, tetapi itu tidak dijamin oleh pendekatan POSIX, yang menyatakan (sebagaimana diekspos dalam respons lain) bahwa hanya id proses sistem yang dicadangkan untuk tujuan itu. Hampir tidak ada implementasi posix yang melakukan ini, dan Anda dapat mengasumsikan dalam sistem turunan unix asli yang memiliki 1respon getppid(2)panggilan sistem sudah cukup untuk menganggap prosesnya yatim. Cara lain untuk mengecek adalah dengan membuat getppid(2)setelah garpu dan membandingkan nilai itu dengan hasil dari panggilan baru. Ini sama sekali tidak bekerja dalam semua kasus, karena kedua panggilan tidak bersama-sama atomik, dan proses induk bisa mati setelah fork(2)dan sebelum getppid(2)panggilan sistem pertama . Proses parent id only changes once, when its parent does ankeluar (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) `, tetapi Anda dapat mengasumsikan dengan aman proses-proses ini tidak memiliki orangtua juga (kecuali ketika Anda mengganti dalam sistem proses init)
Luis Colorado
sumber
-1

Saya telah melewati pid orangtua menggunakan lingkungan ke anak, kemudian secara berkala memeriksa apakah / proc / $ ppid ada dari anak.

Sergei Kirjanov
sumber