Làm thế nào để làm cho quá trình con chết sau khi cha mẹ thoát?


209

Giả sử tôi có một quy trình sinh ra chính xác một quy trình con. Bây giờ khi quá trình cha mẹ thoát ra vì bất kỳ lý do gì (bình thường hoặc bất thường, bằng cách giết, ^ C, khẳng định thất bại hoặc bất cứ điều gì khác) tôi muốn quá trình con chết. Làm thế nào để làm điều đó một cách chính xác?


Một số câu hỏi tương tự trên stackoverflow:


Một số câu hỏi tương tự trên stackoverflow cho Windows :

Câu trả lời:


189

Trẻ có thể yêu cầu hạt nhân phát SIGHUP(hoặc tín hiệu khác) khi cha mẹ chết bằng cách chỉ định tùy chọn PR_SET_PDEATHSIGtrong prctl()tòa nhà như thế này:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Xem man 2 prctlđể biết chi tiết.

Chỉnh sửa: Đây chỉ là Linux


5
Đây là một giải pháp kém vì cha mẹ có thể đã chết. Điều kiện của cuộc đua. Giải pháp đúng: stackoverflow.com/a/17589555/412080
Maxim Egorushkin

16
Gọi một câu trả lời không tốt lắm - ngay cả khi nó không giải quyết được tình trạng chủng tộc. Xem câu trả lời của tôi về cách sử dụng prctl()theo cách miễn phí trong điều kiện cuộc đua. Btw, câu trả lời được liên kết bởi Maxim là không chính xác.
maxschlepzig

4
Đây chỉ là một anser sai. Nó sẽ gửi tín hiệu đến tiến trình con tại thời điểm luồng được gọi là fork, chứ không phải khi tiến trình cha chết.
Lothar

2
@Lothar Thật tuyệt khi thấy một số bằng chứng. man prctlnói: Đặt tín hiệu chết của quy trình cha của quá trình gọi thành arg2 (có thể là giá trị tín hiệu trong phạm vi 1..maxsig hoặc 0 để xóa). Đây là tín hiệu mà quá trình gọi sẽ nhận được khi cha mẹ của nó chết. Giá trị này bị xóa đối với con của một ngã ba (2) và (kể từ Linux 2.4.36 / 2.6.23) khi thực hiện nhị phân ID người dùng tập hợp hoặc ID nhóm nhóm.
qrdl

1
@maxschlepzig Cảm ơn liên kết mới. Có vẻ như liên kết trước đó không hợp lệ. Nhân tiện, sau nhiều năm, vẫn không có api cho việc thiết lập các tùy chọn ở phía phụ huynh. Thật đáng tiếc.
rox

68

Tôi đang cố gắng giải quyết vấn đề tương tự, và vì chương trình của tôi phải chạy trên OS X, giải pháp chỉ dành cho Linux không hoạt động với tôi.

Tôi đã đi đến kết luận giống như những người khác trên trang này - không có cách nào tương thích với POSIX để thông báo cho trẻ khi cha mẹ qua đời. Vì vậy, tôi đã loại bỏ điều tốt nhất tiếp theo - có cuộc thăm dò ý kiến ​​trẻ em.

Khi một quá trình cha mẹ chết (vì bất kỳ lý do nào), quá trình cha mẹ của đứa trẻ trở thành quá trình 1. Nếu đứa trẻ chỉ cần thăm dò định kỳ, nó có thể kiểm tra xem cha mẹ của nó có phải là 1. Nếu có, đứa trẻ nên thoát ra.

Điều này không tốt, nhưng nó hoạt động và dễ dàng hơn các giải pháp bỏ phiếu của socket / lockfile TCP được đề xuất ở nơi khác trên trang này.


6
Giải pháp tuyệt vời. Tiếp tục gọi getppid () cho đến khi nó trả về 1 và sau đó thoát. Điều này là tốt và bây giờ tôi cũng sử dụng nó. Một giải pháp không pollig sẽ là tốt mặc dù. Cảm ơn bạn Schof.
neoneye

10
Chỉ để biết thông tin, trên Solaris nếu bạn ở trong một khu vực, gettpid()nó không trở thành 1 mà có bộ pidlập lịch (quy trình zsched) của khu vực .
Patrick Schlüter

4
Nếu bất cứ ai thắc mắc, trong hệ thống Android, pid dường như là 0 (process pid) thay vì 1, khi cha mẹ chết.
Rui Marques

2
Để có cách làm độc lập mạnh mẽ và nền tảng hơn, trước khi fork () - ing, chỉ cần getpid () và nếu getppid () từ con là khác, hãy thoát.
Sebastien

2
Điều này không hoạt động nếu bạn không kiểm soát quá trình con. Chẳng hạn, tôi đang làm việc với một lệnh bao bọc find (1) và tôi muốn chắc chắn rằng find sẽ bị giết nếu trình bao bọc chết vì một số lý do.
Lucretiel

34

Tôi đã đạt được điều này trong quá khứ bằng cách chạy mã "gốc" trong "con" và mã "sinh ra" trong "cha mẹ" (nghĩa là: bạn đảo ngược ý nghĩa thông thường của bài kiểm tra sau fork()). Sau đó bẫy SIGCHLD trong mã "sinh ra" ...

Có thể không thể trong trường hợp của bạn, nhưng dễ thương khi nó hoạt động.


Giải pháp rất hay, cảm ơn! Cái hiện được chấp nhận là chung chung hơn, nhưng cái của bạn dễ mang theo hơn.
Paweł Hajdan

1
Vấn đề lớn với việc thực hiện công việc ở cha mẹ là bạn đang thay đổi quy trình cha mẹ. Trong trường hợp máy chủ phải chạy "mãi mãi", đó không phải là một lựa chọn.
Alexis Wilke

29

Nếu bạn không thể sửa đổi quy trình con, bạn có thể thử một số thứ như sau:

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

Điều này chạy đứa trẻ từ trong một quy trình vỏ với kích hoạt kiểm soát công việc. Quá trình con được sinh ra trong nền. Shell chờ đợi một dòng mới (hoặc EOF) sau đó giết chết đứa trẻ.

Khi cha mẹ chết - bất kể lý do là gì - nó sẽ đóng đầu ống. Shell con sẽ nhận được EOF từ quá trình đọc và tiến hành để giết tiến trình con nền.


2
Đẹp, nhưng năm cuộc gọi hệ thống, và một sh ​​sinh ra trong mười dòng mã cho phép tôi có một chút hoài nghi về đoạn trình diễn mã này.
Oleiade

+1. Bạn có thể tránh dup2và chiếm lấy stdin bằng cách sử dụng read -ucờ để đọc từ một mô tả tệp cụ thể. Tôi cũng đã thêm một setpgid(0, 0)đứa trẻ để ngăn nó thoát ra khi nhấn ^ C trong thiết bị đầu cuối.
Greg Hewgill

Thứ tự đối số của dup2()cuộc gọi là sai. Nếu bạn muốn sử dụng pipes[0]như stdin bạn phải viết dup2(pipes[0], 0)thay vì dup2(0, pipes[0]). Đó là dup2(oldfd, newfd)nơi cuộc gọi đóng một newfd đã mở trước đó.
maxschlepzig

@Oleiade, tôi đồng ý, đặc biệt vì sh sinh ra chỉ là một ngã ba khác để thực hiện quy trình con thực sự ...
maxschlepzig

16

Trong Linux, bạn có thể cài đặt tín hiệu tử vong cha mẹ ở trẻ, ví dụ:

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

Lưu ý rằng việc lưu trữ id tiến trình cha trước ngã ba và kiểm tra nó ở trẻ sau khi prctl()loại bỏ điều kiện chạy đua giữa prctl()và thoát khỏi quá trình gọi là con.

Cũng lưu ý rằng tín hiệu tử vong của cha mẹ của đứa trẻ sẽ bị xóa ở những đứa trẻ mới được tạo ra. Nó không bị ảnh hưởng bởi một execve().

Thử nghiệm đó có thể được đơn giản hóa nếu chúng tôi chắc chắn rằng quy trình hệ thống chịu trách nhiệm áp dụng tất cả các trẻ mồ côi có 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 ...

Tuy nhiên, dựa vào quá trình hệ thống đó initvà có PID 1 không khả dụng. POSIX.1-2008 chỉ định :

ID tiến trình cha của tất cả các quy trình con hiện tại và các quy trình zombie của quy trình gọi sẽ được đặt thành ID quy trình của quy trình hệ thống do người thực hiện xác định. Đó là, các quy trình này sẽ được kế thừa bởi một quy trình hệ thống đặc biệt.

Theo truyền thống, quy trình hệ thống chấp nhận tất cả trẻ mồ côi là PID 1, tức là init - là tổ tiên của tất cả các quy trình.

Trên các hệ thống hiện đại như Linux hoặc FreeBSD, một quy trình khác có thể có vai trò đó. Ví dụ, trên Linux, một quy trình có thể gọi prctl(PR_SET_CHILD_SUBREAPER, 1)để thiết lập chính nó như là quy trình hệ thống kế thừa tất cả trẻ mồ côi của bất kỳ hậu duệ nào của nó (xem ví dụ về Fedora 25).


Tôi không hiểu "Bài kiểm tra đó có thể được đơn giản hóa nếu chúng tôi chắc chắn rằng ông bà luôn là quá trình init". Khi một tiến trình cha mẹ chết đi, một tiến trình trở thành con của tiến trình init (pid 1), không phải là con của ông bà, phải không? Vì vậy, bài kiểm tra dường như luôn luôn đúng.
Julian Schaub - litb


thú vị, cảm ơn Mặc dù vậy, tôi không thấy nó phải làm gì với ông bà.
Julian Schaub - litb

1
@ JohannesSchaub-litb, bạn không thể luôn cho rằng độ hạt của một tiến trình sẽ là init(8)tiến trình .... điều duy nhất bạn có thể giả sử là khi một tiến trình cha mẹ chết, là id cha của nó sẽ thay đổi. Điều này thực sự xảy ra một lần trong đời của một quá trình .... và là khi cha mẹ của quá trình đó chết. Chỉ có một ngoại lệ chính cho điều này, và dành cho init(8)trẻ em, nhưng bạn được bảo vệ khỏi điều này, như init(8)chưa bao giờ exit(2)(sự hoảng loạn hạt nhân trong trường hợp đó)
Luis Colorado

1
Thật không may, nếu một đứa trẻ chuyển từ một luồng, và sau đó thoát khỏi luồng, tiến trình con sẽ nhận được SIGTERM.
rox

14

Vì lợi ích hoàn toàn. Trên macOS, bạn có thể sử dụng 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);
}

Bạn có thể thực hiện việc này với API đẹp hơn một chút, bằng cách sử dụng các nguồn điều phối với DISPATCH_SOURCE_PROC và PROC_EXIT.
Nga

Vì lý do gì, điều này đang khiến máy Mac của tôi hoảng loạn. Chạy một quy trình với mã này có khả năng đóng băng 50%, khiến cho các quạt quay với tốc độ mà tôi chưa từng nghe thấy trước đây (siêu nhanh), và sau đó mac tắt. HÃY CẨN THẬN VỚI MÃ SỐ NÀY .
Qix - MONICA ĐƯỢC PHÂN PHỐI

Có vẻ như trên macOS của tôi, tiến trình con thoát ra tự động sau khi thoát khỏi cha mẹ. Tôi không biết tại sao.
Yi Lin Liu

@YiLinLiu iirc Tôi đã sử dụng NSTaskhoặc posix spawn. Xem startTaskchức năng trong mã của tôi ở đây: github.com/neoneye/newton-commander-browse/blob/master/C
Cầu / Tiết

11

Quá trình con có một đường ống đến / từ quá trình cha mẹ không? Nếu vậy, bạn sẽ nhận được SIGPIPE nếu viết hoặc nhận EOF khi đọc - những điều kiện này có thể được phát hiện.


1
Tôi thấy điều này đã không xảy ra một cách đáng tin cậy, ít nhất là trên OS X.
Schof

Tôi đến đây để thêm câu trả lời này. Đây chắc chắn là giải pháp tôi sử dụng cho trường hợp này.
Dolda2000

Điểm thận trọng: systemd vô hiệu hóa SIGPIPE theo mặc định trong các dịch vụ mà nó quản lý, nhưng bạn vẫn có thể kiểm tra việc đóng ống. Xem freedesktop.org/software/systemd/man/systemd.exec.html trong IgnoreSIGPIPE
jdizzle

11

Lấy cảm hứng từ một câu trả lời khác ở đây, tôi đã đưa ra giải pháp all-POSIX sau đây. Ý tưởng chung là tạo ra một quá trình trung gian giữa cha mẹ và đứa trẻ, có một mục đích: Thông báo khi cha mẹ chết, và giết chết đứa trẻ một cách rõ ràng.

Loại giải pháp này hữu ích khi mã ở trẻ không thể sửa đổi.

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

Có hai hãy cẩn thận với phương pháp này:

  • Nếu bạn cố tình giết quá trình trung gian, thì đứa trẻ sẽ không bị giết khi cha mẹ chết.
  • Nếu đứa trẻ thoát ra trước cha mẹ, thì quá trình trung gian sẽ cố gắng giết chết con ban đầu, giờ đây có thể đề cập đến một quy trình khác. (Điều này có thể được sửa với nhiều mã hơn trong quy trình trung gian.)

Bên cạnh đó, mã thực tế tôi đang sử dụng là bằng Python. Đây là sự hoàn chỉnh:

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)

Lưu ý rằng một thời gian trước, dưới IRIX, tôi đã sử dụng sơ đồ cha / con trong đó tôi có một đường ống giữa cả hai và đọc từ đường ống tạo ra SIGHUP nếu một trong hai bị chết. Đó là cách tôi đã sử dụng để giết những đứa trẻ của mình () mà không cần quá trình trung gian.
Alexis Wilke

Tôi nghĩ rằng cảnh báo thứ hai của bạn là sai. Pid của một đứa trẻ là một tài nguyên thuộc về cha mẹ của nó và nó không thể được giải phóng / tái sử dụng cho đến khi cha mẹ (quá trình trung gian) chờ đợi nó (hoặc chấm dứt và để init chờ đợi nó).
R .. GitHub DỪNG GIÚP ICE

7

Tôi không tin rằng có thể đảm bảo rằng chỉ sử dụng các cuộc gọi POSIX tiêu chuẩn. Giống như cuộc sống thực, một khi một đứa trẻ được sinh ra, nó có một cuộc sống của riêng nó.

có thể cho quá trình cha mẹ để bắt sự kiện chấm dứt nhất có thể, và cố gắng để giết chết quá trình trẻ em tại thời điểm đó, nhưng luôn có một số mà không thể bị bắt.

Ví dụ, không có quá trình có thể bắt a SIGKILL. Khi kernel xử lý tín hiệu này, nó sẽ giết tiến trình đã chỉ định mà không có thông báo nào cho quá trình đó.

Để mở rộng sự tương tự - cách làm tiêu chuẩn duy nhất khác là đứa trẻ tự tử khi nhận thấy rằng nó không còn cha mẹ.

Có một cách duy nhất để làm điều đó với Linux prctl(2)- xem các câu trả lời khác.


6

Như những người khác đã chỉ ra, việc dựa vào pid cha mẹ để trở thành 1 khi cha mẹ thoát ra là không di động. Thay vì chờ ID tiến trình cha cụ thể, chỉ cần đợi ID thay đổi:

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

Thêm một giấc ngủ vi mô như mong muốn nếu bạn không muốn thăm dò ý kiến ​​ở tốc độ tối đa.

Tùy chọn này có vẻ đơn giản với tôi hơn là sử dụng đường ống hoặc dựa vào tín hiệu.


Thật không may, giải pháp đó không mạnh mẽ. Điều gì xảy ra nếu quá trình cha mẹ chết trước khi bạn nhận được giá trị ban đầu? Đứa trẻ sẽ không bao giờ thoát ra.
dgatwood 18/03/2015

@dgatwood, ý bạn là gì?!? Việc đầu tiên getpid()được thực hiện ở phụ huynh trước khi gọi fork(). Nếu cha mẹ chết trước đó thì đứa trẻ không tồn tại. Điều gì có thể xảy ra là đứa trẻ ra ngoài sống cha mẹ một thời gian.
Alexis Wilke

Trong ví dụ hơi khó hiểu này, nó hoạt động, nhưng trong mã trong thế giới thực, fork gần như luôn luôn được theo sau bởi exec và quá trình mới phải bắt đầu lại bằng cách yêu cầu PPID của nó. Trong khoảng thời gian giữa hai lần kiểm tra đó, nếu cha mẹ đi vắng, đứa trẻ sẽ không biết gì. Ngoài ra, bạn không có khả năng kiểm soát cả mã cha và mã con (nếu không bạn chỉ có thể chuyển PPID làm đối số). Vì vậy, như một giải pháp chung, phương pháp đó không hoạt động tốt. Và thực tế, nếu một hệ điều hành giống như UNIX xuất hiện mà không có init là 1, rất nhiều thứ sẽ phá vỡ đến nỗi tôi không thể tưởng tượng được ai đang làm điều đó.
dgatwood 30/03/2015

pass cha pid là đối số dòng lệnh khi thực hiện exec cho con.
Nish

2
Bỏ phiếu ở tốc độ tối đa là điên rồ.
maxschlepzig

4

Cài đặt trình xử lý bẫy để bắt SIGINT, nó sẽ giết chết quá trình con của bạn nếu nó vẫn còn sống, mặc dù các áp phích khác là chính xác rằng nó sẽ không bắt SIGKILL.

Mở một .lockfile với quyền truy cập độc quyền và có cuộc thăm dò ý kiến ​​con trên đó cố gắng mở nó - nếu việc mở thành công, tiến trình con sẽ thoát


Hoặc, đứa trẻ có thể mở lockfile trong một luồng riêng biệt, trong chế độ chặn, trong trường hợp này đây có thể là một giải pháp khá hay và sạch sẽ. Có lẽ nó có một số hạn chế tính di động mặc dù.
Jean

4

Giải pháp này hiệu quả với tôi:

  • Truyền ống stdin cho con - bạn không phải ghi bất kỳ dữ liệu nào vào luồng.
  • Trẻ đọc vô thời hạn từ stdin cho đến EOF. Một tín hiệu EOF mà cha mẹ đã đi.
  • Đây là cách dễ dàng và di động để phát hiện khi cha mẹ đã đi. Ngay cả khi cha mẹ gặp sự cố, hệ điều hành sẽ đóng đường ống.

Điều này là cho một quá trình kiểu công nhân mà sự tồn tại của nó chỉ có ý nghĩa khi cha mẹ còn sống.


@SebastianJylanki Tôi không nhớ nếu tôi đã thử, nhưng nó có thể hoạt động vì các nguyên thủy (luồng POSIX) khá chuẩn trên các HĐH.
joonas.fi

3

Tôi nghĩ rằng một cách nhanh chóng và bẩn thỉu là tạo ra một đường ống giữa trẻ và cha mẹ. Khi cha mẹ thoát ra, trẻ em sẽ nhận được SIGPIPE.


SIGPIPE không được gửi trên đường ống gần, nó chỉ được gửi khi đứa trẻ cố gắng viết cho nó.
Alcaro

3

Một số áp phích đã đề cập đến ống và kqueue. Trong thực tế, bạn cũng có thể tạo một cặp ổ cắm tên miền Unix được kết nối bằng socketpair()cuộc gọi. Các loại ổ cắm nên được SOCK_STREAM.

Giả sử bạn có hai mô tả tệp socket fd1, fd2. Bây giờ fork()để tạo quá trình con, sẽ kế thừa các fds. Ở cha mẹ bạn đóng fd2 và ở con bạn đóng fd1. Bây giờ mỗi quá trình có thể poll()mở fd còn lại vào cuối của POLLINsự kiện. Miễn là mỗi bên không rõ ràng close()fd của mình trong suốt cuộc đời bình thường, bạn có thể khá chắc chắn rằng một POLLHUPlá cờ sẽ chỉ ra sự chấm dứt của bên kia (không có vấn đề sạch hay không). Khi được thông báo về sự kiện này, đứa trẻ có thể quyết định phải làm gì (ví dụ như chết).

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

Bạn có thể thử biên dịch mã bằng chứng khái niệm ở trên và chạy nó trong một thiết bị đầu cuối như thế nào ./a.out &. Bạn có khoảng 100 giây để thử nghiệm việc giết chết cha mẹ bằng nhiều tín hiệu khác nhau, hoặc đơn giản là nó sẽ thoát. Trong cả hai trường hợp, bạn sẽ thấy thông báo "con: cha mẹ cúp máy".

So với phương thức sử dụng SIGPIPEtrình xử lý, phương thức này không yêu cầu thử write()cuộc gọi.

Phương pháp này cũng đối xứng , tức là các quy trình có thể sử dụng cùng một kênh để theo dõi sự tồn tại của nhau.

Giải pháp này chỉ gọi các chức năng POSIX. Tôi đã thử điều này trong Linux và FreeBSD. Tôi nghĩ rằng nó nên hoạt động trên các Unix khác nhưng tôi chưa thực sự thử nghiệm.

Xem thêm:

  • unix(7)Linux trang người đàn ông, unix(4)cho FreeBSD, poll(2), socketpair(2), socket(7)trên Linux.

Rất tuyệt, tôi thực sự tự hỏi nếu điều này có bất kỳ vấn đề độ tin cậy mặc dù. Bạn đã thử nghiệm điều này trong sản xuất? Với các ứng dụng khác nhau?
Aktau

@Aktau, tôi đã sử dụng Python tương đương với thủ thuật này trong một chương trình Linux. Tôi cần nó bởi vì logic làm việc của trẻ là "làm sạch nỗ lực tốt nhất sau khi cha mẹ thoát ra và sau đó cũng thoát". Tuy nhiên, tôi thực sự không chắc chắn về các nền tảng khác. Đoạn mã C hoạt động trên Linux và FreeBSD nhưng đó là tất cả những gì tôi biết ... Ngoài ra, có những trường hợp bạn nên cẩn thận, chẳng hạn như cha mẹ lại yêu cầu hoặc cha mẹ từ bỏ fd trước khi thực sự thoát ra (do đó tạo ra một cửa sổ thời gian cho điều kiện của cuộc đua).
Công Ma

@Aktau - Điều này sẽ hoàn toàn đáng tin cậy.
Omnifarious

1

Dưới POSIX , các exit(), _exit()_Exit()các chức năng được định nghĩa để:

  • Nếu quy trình là một quy trình kiểm soát, tín hiệu SIGHUP sẽ được gửi đến từng quy trình trong nhóm quy trình nền trước của thiết bị đầu cuối điều khiển thuộc quy trình gọi.

Vì vậy, nếu bạn sắp xếp để quy trình cha mẹ trở thành quy trình kiểm soát cho nhóm quy trình của nó, thì đứa trẻ sẽ nhận được tín hiệu SIGHUP khi cha mẹ thoát ra. Tôi không chắc chắn điều đó xảy ra khi cha mẹ gặp sự cố, nhưng tôi nghĩ nó xảy ra. Chắc chắn, đối với các trường hợp không va chạm, nó sẽ hoạt động tốt.

Lưu ý rằng bạn có thể phải đọc khá nhiều chữ in nhỏ - bao gồm phần cơ sở định nghĩa (Định nghĩa), cũng như các thông tin dịch vụ hệ thống cho exit()setsid()setpgrp()- để có được bức tranh hoàn chỉnh. (Tôi cũng vậy!)


3
Hừm. Tài liệu này rất mơ hồ và mâu thuẫn về vấn đề này, nhưng có vẻ như quy trình cha mẹ phải là quy trình chính cho phiên, không chỉ nhóm quy trình. Quá trình khách hàng tiềm năng cho phiên luôn luôn đăng nhập và việc quy trình của tôi tiếp quản vì quy trình dẫn đầu cho phiên mới vượt quá khả năng của tôi vào lúc này.
Schof

2
SIGHUP chỉ có hiệu quả được gửi đến các tiến trình con nếu quá trình thoát là vỏ đăng nhập. opengroup.org/onlinepub/009695399/fifts/exit.html "Việc chấm dứt một quá trình không trực tiếp chấm dứt con cái của nó. Việc gửi tín hiệu SIGHUP như được mô tả dưới đây gián tiếp chấm dứt trẻ em / trong một số trường hợp /."
Rob K

1
@Rob: đúng - đó cũng là những gì trích dẫn tôi đã nói: chỉ trong một số trường hợp, quá trình con mới có được SIGHUP. Và nó hoàn toàn là một sự đơn giản hóa quá mức để nói rằng đó chỉ là một vỏ đăng nhập gửi SIGHUP, mặc dù đó là trường hợp phổ biến nhất. Nếu một quá trình có nhiều con tự thiết lập thành quá trình kiểm soát cho chính nó và các con của nó, thì SIGHUP sẽ (thuận tiện) được gửi cho con của nó khi chủ chết. OTOH, các quy trình hiếm khi gặp phải nhiều rắc rối như vậy - vì vậy tôi chọn nhiều nit hơn là đưa ra một ngụy biện thực sự quan trọng.
Jonathan Leffler

2
Tôi đã chơi đùa với nó trong vài giờ và không thể làm cho nó hoạt động được. Nó sẽ xử lý tốt một trường hợp tôi có một daemon với một số đứa trẻ mà tất cả cần phải chết khi cha mẹ thoát ra.
Rob K

1

Nếu bạn gửi tín hiệu đến pid 0, ví dụ sử dụng

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

tín hiệu đó được gửi đến toàn bộ nhóm quá trình, do đó giết chết đứa trẻ một cách hiệu quả.

Bạn có thể kiểm tra nó dễ dàng với một cái gì đó như:

(cat && kill 0) | python

Nếu sau đó bạn nhấn ^ D, bạn sẽ thấy văn bản "Terminated"như một dấu hiệu cho thấy trình thông dịch Python thực sự đã bị giết, thay vì chỉ thoát vì stdin bị đóng.


1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" vui vẻ in 4 thay vì chấm dứt
Kamil Szot

1

Trong trường hợp nó có liên quan đến bất kỳ ai khác, khi tôi sinh ra các cá thể JVM trong các tiến trình con rẽ nhánh từ C ++, cách duy nhất tôi có thể khiến các cá thể JVM chấm dứt đúng cách sau khi quá trình cha mẹ hoàn thành là làm như sau. Hy vọng ai đó có thể cung cấp phản hồi trong các bình luận nếu đây không phải là cách tốt nhất để làm điều này.

1) Gọi prctl(PR_SET_PDEATHSIG, SIGHUP)quy trình con rẽ nhánh như được đề xuất trước khi khởi chạy ứng dụng Java thông qua execv

2) Thêm một móc tắt máy vào ứng dụng Java để thăm dò ý kiến ​​cho đến khi bộ cha mẹ của nó bằng 1, sau đó thực hiện một thao tác khó Runtime.getRuntime().halt(0). Việc bỏ phiếu được thực hiện bằng cách khởi chạy một trình bao riêng biệt chạy pslệnh (Xem: Làm cách nào để tôi tìm thấy PID của mình trong Java hoặc JRuby trên Linux? ).

EDIT 130118:

Có vẻ như đó không phải là một giải pháp mạnh mẽ. Tôi vẫn đang cố gắng một chút để hiểu được các sắc thái của những gì đang diễn ra, nhưng đôi khi tôi vẫn nhận được các quy trình JVM mồ côi khi chạy các ứng dụng này trong các phiên màn hình / SSH.

Thay vì bỏ phiếu cho PPID trong ứng dụng Java, tôi chỉ đơn giản là móc tắt máy thực hiện việc dọn dẹp theo sau là dừng cứng như trên. Sau đó, tôi chắc chắn sẽ gọi waitpidứng dụng mẹ C ++ trong quy trình con sinh ra khi đến lúc chấm dứt mọi thứ. Đây dường như là một giải pháp mạnh mẽ hơn, vì quy trình con đảm bảo rằng nó chấm dứt, trong khi cha mẹ sử dụng các tham chiếu hiện có để đảm bảo rằng con của nó chấm dứt. So sánh giải pháp này với giải pháp trước đây đã khiến quá trình cha mẹ chấm dứt bất cứ khi nào nó hài lòng và các con cố gắng tìm hiểu xem chúng có phải mồ côi trước khi chấm dứt hay không.


1
Sự PID equals 1chờ đợi không hợp lệ. Cha mẹ mới có thể là một số PID khác. Bạn nên kiểm tra xem nó có thay đổi từ cha mẹ ban đầu (getpid () trước ngã ba ()) sang cha mẹ mới (getppid () ở con không bằng getpid () khi được gọi trước ngã ba ()).
Alexis Wilke

1

Một cách khác để làm điều này là đặc thù của Linux là tạo cha mẹ trong không gian tên PID mới. Sau đó, nó sẽ là PID 1 trong không gian tên đó và khi thoát ra, tất cả trẻ em của nó sẽ bị giết ngay lập tức SIGKILL.

Thật không may, để tạo ra một không gian tên PID mới, bạn phải có CAP_SYS_ADMIN. Nhưng, phương pháp này rất hiệu quả và không đòi hỏi phải thay đổi thực sự đối với cha mẹ hoặc con cái ngoài sự ra mắt ban đầu của cha mẹ.

Xem bản sao (2) , pid_namespaces (7)unshare (2) .


Tôi cần chỉnh sửa theo cách khác. Có thể sử dụng prctl để thực hiện quy trình hoạt động như quy trình khởi đầu cho tất cả trẻ em và cháu chắt, và cháu chắt, v.v ...
Omnifarious

0

Nếu cha mẹ chết, PPID của trẻ mồ côi thay đổi thành 1 - bạn chỉ cần kiểm tra PPID của chính mình. Theo một cách nào đó, đây là bỏ phiếu, đã đề cập ở trên. đây là mảnh vỏ cho điều đó:

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

Tôi tìm thấy 2 giải pháp, cả hai đều không hoàn hảo.

1. Giết tất cả trẻ em bằng cách giết (-pid) khi nhận được tín hiệu SIGTERM.
Rõ ràng, giải pháp này không thể xử lý "kill -9", nhưng nó hoạt động trong hầu hết các trường hợp và rất đơn giản vì nó không cần phải nhớ tất cả các quy trình con.


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

Theo cách tương tự, bạn có thể cài đặt trình xử lý 'exit' như cách trên nếu bạn gọi process.exit ở đâu đó. Lưu ý: Ctrl + C và sự cố bất ngờ đã được hệ điều hành tự động xử lý để tiêu diệt nhóm quá trình, vì vậy không còn ở đây nữa.

2.Sử dụng chjj / pty.js để sinh ra quy trình của bạn với thiết bị đầu cuối kiểm soát được đính kèm.
Khi bạn giết tiến trình hiện tại bằng cách nào đó thậm chí giết -9, tất cả các tiến trình con cũng sẽ tự động bị giết (bởi HĐH?). Tôi đoán rằng vì quy trình hiện tại giữ một mặt khác của thiết bị đầu cuối, vì vậy nếu quy trình hiện tại chết, quy trình con sẽ nhận được SIGPIPE nên chết.


    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

Tôi quản lý để thực hiện một giải pháp di động, không bỏ phiếu với 3 quy trình bằng cách lạm dụng kiểm soát thiết bị đầu cuối và phiên. Đây là thủ dâm tinh thần, nhưng hoạt động.

Bí quyết là:

  • quá trình A được bắt đầu
  • quá trình A tạo ra một ống P (và không bao giờ đọc từ nó)
  • quy trình A chuyển thành quy trình B
  • quá trình B tạo ra một phiên mới
  • quá trình B phân bổ một thiết bị đầu cuối ảo cho phiên mới đó
  • process B cài đặt trình xử lý SIGCHLD để chết khi đứa trẻ thoát ra
  • process B thiết lập trình xử lý SIGPIPE
  • quy trình B chuyển thành quy trình C
  • process C làm bất cứ điều gì nó cần (ví dụ exec () s nhị phân chưa sửa đổi hoặc chạy bất kỳ logic nào)
  • quá trình B ghi vào ống P (và chặn theo cách đó)
  • process A Wait () s trên process B và thoát khi nó chết

Theo cách đó:

  • nếu tiến trình A chết: tiến trình B nhận được SIGPIPE và chết
  • nếu tiến trình B chết: process A Wait () return and die, process C nhận được SIGHUP (bởi vì khi người đứng đầu phiên của phiên có thiết bị đầu cuối được gắn chết, tất cả các quy trình trong nhóm quy trình tiền cảnh đều nhận được SIGHUP)
  • nếu tiến trình C chết: tiến trình B bị SIGCHLD và chết, vì vậy tiến trình A chết

Thiếu sót:

  • quá trình C không thể xử lý SIGHUP
  • quá trình C sẽ được chạy trong một phiên khác
  • process C không thể sử dụng API nhóm phiên / quy trình vì nó sẽ phá vỡ thiết lập dễ vỡ
  • tạo một thiết bị đầu cuối cho mọi hoạt động như vậy không phải là ý tưởng tốt nhất

0

Mặc dù đã 7 năm trôi qua, tôi vẫn gặp phải vấn đề này khi tôi đang chạy ứng dụng SpringBoot cần khởi động máy chủ webpack-dev trong quá trình phát triển và cần phải xử lý nó khi quá trình phụ trợ dừng lại.

Tôi cố gắng sử dụng Runtime.getRuntime().addShutdownHooknhưng nó hoạt động trên Windows 10 nhưng không hoạt động trên Windows 7.

Tôi đã thay đổi nó để sử dụng một luồng chuyên dụng chờ quá trình thoát hoặc InterruptedExceptiondường như hoạt động chính xác trên cả hai phiên bản 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();
}

0

Trong lịch sử, từ UNIX v7, hệ thống quy trình đã phát hiện ra sự mồ côi của các quy trình bằng cách kiểm tra id gốc của quy trình. Như tôi nói, trong lịch sử, init(8)quy trình hệ thống là một quy trình đặc biệt chỉ bởi một lý do: Nó không thể chết. Nó không thể chết vì thuật toán kernel để xử lý việc gán id tiến trình cha mới, phụ thuộc vào thực tế này. khi một tiến trình thực hiện exit(2)cuộc gọi của nó (bằng cách gọi hệ thống quy trình hoặc bằng tác vụ bên ngoài như gửi tín hiệu hoặc tương tự), hạt nhân sẽ gán lại cho tất cả các con của quá trình này id của tiến trình init dưới dạng id tiến trình cha của chúng. Điều này dẫn đến thử nghiệm dễ dàng nhất và cách dễ nhất để biết liệu một quá trình có mồ côi hay không. Chỉ cần kiểm tra kết quả của getppid(2)cuộc gọi hệ thống và nếu đó là id quá trình củainit(2) quá trình sau đó quá trình có mồ côi trước khi hệ thống gọi.

Hai vấn đề nổi lên từ phương pháp này có thể dẫn đến các vấn đề:

  • đầu tiên, chúng tôi có khả năng thay đổi initquy trình thành bất kỳ quy trình người dùng nào, vậy làm thế nào chúng tôi có thể đảm bảo rằng quy trình init sẽ luôn là cha mẹ của tất cả các quy trình mồ côi? Chà, trong exitmã cuộc gọi hệ thống có một kiểm tra rõ ràng để xem liệu quy trình thực hiện cuộc gọi có phải là quy trình init không (quy trình có pid bằng 1) và nếu đó là trường hợp, thì nhân hoảng loạn (Nó không thể duy trì được nữa hệ thống phân cấp quy trình) vì vậy không cho phép tiến trình init thực hiện exit(2)cuộc gọi.
  • thứ hai, có một điều kiện cuộc đua trong bài kiểm tra cơ bản được trình bày ở trên. Id của quá trình ban đầu được giả sử theo lịch sử 1, nhưng điều đó không được bảo đảm bởi phương pháp POSIX, nói rằng (như được trình bày trong phản hồi khác) rằng chỉ có id quy trình của hệ thống được dành riêng cho mục đích đó. Hầu như không có triển khai posix nào thực hiện điều này và bạn có thể giả sử trong các hệ thống dẫn xuất unix gốc có 1phản hồi của getppid(2)lệnh gọi hệ thống là đủ để cho rằng quá trình này là mồ côi. Một cách khác để kiểm tra là thực hiện getppid(2)ngay sau ngã ba và so sánh giá trị đó với kết quả của một cuộc gọi mới. Điều này chỉ đơn giản là không hoạt động trong mọi trường hợp, vì cả hai cuộc gọi không phải là nguyên tử cùng nhau và quá trình cha mẹ có thể chết sau fork(2)và trước getppid(2)cuộc gọi hệ thống đầu tiên . Quá trình parent id only changes once, when its parent does anthoát (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) `, nhưng bạn có thể giả sử một cách an toàn các quy trình này là không có cha mẹ (trừ khi bạn thay thế trong hệ thống quy trình init)

-1

Tôi đã truyền pid cha mẹ bằng cách sử dụng môi trường cho đứa trẻ, sau đó kiểm tra định kỳ nếu / Proc / $ ppid tồn tại từ đứa trẻ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.