Điều gì xảy ra khi gửi SIGKILL đến Quá trình Zombie trong Linux?


10

Trong Linux, khi một tiến trình con kết thúc và cha mẹ chưa đợi nó, nó sẽ trở thành một tiến trình zombie. Mã thoát của trẻ em được lưu trữ trong mô tả pid.

Nếu a SIGKILLđược gửi cho đứa trẻ, sẽ không có bất kỳ ảnh hưởng nào.

Điều này có nghĩa là mã thoát sẽ không được sửa đổi bởi SIGKILLhoặc mã thoát sẽ được sửa đổi để chỉ ra rằng đứa trẻ đã thoát vì nhận được một SIGKILL?

Câu trả lời:


14

Để trả lời câu hỏi đó, bạn phải hiểu cách các tín hiệu được gửi đến một quy trình và làm thế nào một quy trình tồn tại trong kernel.

Mỗi quá trình được biểu diễn dưới dạng task_structbên trong kernel (định nghĩa nằm trong sched.htệp tiêu đề và bắt đầu ở đây ). Cấu trúc đó chứa thông tin về quy trình; ví dụ như pid. Thông tin quan trọng nằm ở dòng 1566 nơi tín hiệu liên quan được lưu trữ. Điều này chỉ được đặt nếu tín hiệu được gửi đến quá trình.

Một quá trình chết hoặc một quá trình zombie vẫn có một task_struct. Cấu trúc vẫn còn, cho đến khi quá trình cha mẹ (tự nhiên hoặc bằng cách thông qua) đã được gọi wait()sau khi nhận được SIGCHLDđể gặt hái quá trình con của nó. Khi một tín hiệu được gửi, signal_structđược đặt. Nó không thành vấn đề nếu tín hiệu có thể bắt được hay không, trong trường hợp này.

Tín hiệu được đánh giá mỗi khi quá trình chạy. Hoặc chính xác, trước khi quá trình sẽ chạy. Quá trình sau đó là trong TASK_RUNNINGnhà nước. Hạt nhân chạy schedule()thường trình xác định quá trình chạy tiếp theo theo thuật toán lập lịch của nó. Giả sử quá trình này là quá trình chạy tiếp theo, giá trị của signal_structđược đánh giá, cho dù có tín hiệu chờ xử lý hay không. Nếu một trình xử lý tín hiệu được xác định thủ công (thông qua signal()hoặc sigaction()), chức năng đã đăng ký sẽ được thực thi, nếu không thì hành động mặc định của tín hiệu được thực thi. Hành động mặc định phụ thuộc vào tín hiệu được gửi.

Chẳng hạn, SIGSTOPtrình xử lý mặc định của tín hiệu sẽ thay đổi trạng thái của quy trình hiện tại thành TASK_STOPPEDrồi chạy schedule()để chọn một quy trình mới để chạy. Lưu ý, SIGSTOPlà không thể bắt được (như SIGKILL), do đó không có khả năng đăng ký một trình xử lý tín hiệu thủ công. Trong trường hợp tín hiệu không thể so sánh được, hành động mặc định sẽ luôn được thực thi.


Cho câu hỏi của bạn:

Một quy trình không còn tồn tại hoặc không còn tồn tại sẽ không bao giờ được xác định bởi người lập lịch để ở trong TASK_RUNNINGtrạng thái một lần nữa. Do đó, hạt nhân sẽ không bao giờ chạy trình xử lý tín hiệu (mặc định hoặc được xác định) cho tín hiệu tương ứng, bất kỳ tín hiệu nào là. Do đó, exit_signalsẽ không bao giờ được thiết lập lại. Tín hiệu được "gửi" đến quy trình bằng cách đặt vào signal_structtrong task_structquy trình, nhưng sẽ không có gì khác xảy ra, vì quy trình sẽ không bao giờ chạy lại. Không có mã để chạy, tất cả những gì còn lại của quá trình là quy trình đó.

Tuy nhiên, nếu tiến trình cha mẹ gặt hái con cái của nó wait(), mã thoát mà nó nhận được là mã khi quá trình "ban đầu" bị chết. Sẽ không có vấn đề gì nếu có tín hiệu chờ xử lý.


Có, nhưng lệnh killtự trả về 0 hay 1?
Anthony Rutledge

9

Một quá trình zombie về cơ bản đã chết. Điều duy nhất là chưa ai thừa nhận cái chết của nó nên nó tiếp tục chiếm một mục trong bảng quy trình cũng như khối điều khiển (cấu trúc mà nhân Linux duy trì cho mọi luồng trong hoạt động). Các tài nguyên khác như khóa bắt buộc trên các tệp, phân đoạn bộ nhớ dùng chung, semaphores, v.v. được thu hồi.

Bạn không thể báo hiệu cho họ vì không ai có thể hành động theo tín hiệu này. Ngay cả các tín hiệu gây tử vong như KILL cũng vô dụng vì quá trình đã chấm dứt thực hiện. Bạn có thể tự thử:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
    pid_t pid = fork();

    if (pid == -1)
        exit(-1);

    if (pid > 0) {
        //parent
        printf("[parent]: I'm the parent, the pid of my child is %i\n"
            "I'll start waiting for it in 10 seconds.\n", pid);
        sleep(10);
        int status;
        wait(&status);

        if (WIFSIGNALED(status)) {
            printf("[parent]: My child has died from a signal: %i\n", WTERMSIG(status));
        } else if (WIFEXITED(status)) {
            printf("[parent]: My child has died from natural death\n");
        } else {
            printf("[parent]: I don't know what happened to my child\n");
        }
    } else {
        //child
        printf("[child]: I'm dying soon, try to kill me.\n");
        sleep(5);
        printf("[child]: Dying now!\n");
    }

    return 0;
}

Ở đây, tôi bắt đầu một quá trình rèn và ngủ trước khi chờ đợi đứa con của nó. Đứa trẻ không làm gì ngoài ngủ một chút. Bạn có thể giết đứa trẻ khi nó đang ngủ hoặc ngay sau khi nó thoát ra để thấy sự khác biệt:

$ make zombie 
cc     zombie.c   -o zombie

$ ./zombie    
[parent]: I'm the parent, the pid of my child is 16693
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
# Here, I did "kill -15 16693" in another console
[parent]: My child has died from a signal: 15

$ ./zombie
[parent]: I'm the parent, the pid of my child is 16717
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
[child]: Dying now!
# Here, I did "kill -15 16717" in another console
[parent]: My child has died from natural death

Tôi muốn tìm đoạn văn có liên quan trong mã nguồn kernel cho bạn nhưng tôi đang vật lộn để tìm nó ...
lgeorget

@Igeorget Cảm ơn nhưng không sao, tôi không cần xem mã hạt nhân.
dùng137481

Chương trình Ruby đơn giản tạo ra một quy trình và đứa trẻ thoát ra ngay lập tức ruby -e "loop while fork { exit! }"... imgur.com/SoRXErm
S.Goswami
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.