Làm thế nào để Linux giết chết một quy trình?


91

Nó thường gây trở ngại cho tôi rằng, mặc dù tôi đã làm việc chuyên nghiệp với máy tính trong nhiều thập kỷ và Linux trong một thập kỷ, tôi thực sự coi hầu hết các chức năng của HĐH là một hộp đen, không giống như ma thuật.

Hôm nay tôi đã nghĩ về killlệnh này, và trong khi tôi sử dụng nó nhiều lần mỗi ngày (cả về "bình thường" và -9hương vị của nó) tôi phải thừa nhận rằng tôi hoàn toàn không biết nó hoạt động như thế nào đằng sau hậu trường.

Theo quan điểm của tôi, nếu một quá trình đang chạy bị "treo", tôi gọi killvào PID của nó, và sau đó nó đột nhiên không chạy nữa. Ma thuật!

Điều gì thực sự xảy ra ở đó? Các trang nói về "tín hiệu" nhưng chắc chắn đó chỉ là một sự trừu tượng. Gửi kill -9đến một quy trình không yêu cầu sự hợp tác của quy trình (như xử lý tín hiệu), nó chỉ giết chết nó.

  • Làm thế nào để Linux ngăn quá trình tiếp tục chiếm thời gian CPU?
  • Có phải nó được loại bỏ khỏi lịch trình?
  • Liệu nó ngắt kết nối quá trình từ xử lý tập tin mở của nó?
  • Bộ nhớ ảo của quá trình được giải phóng như thế nào?
  • Có thứ gì đó giống như một bảng toàn cầu trong bộ nhớ, trong đó Linux lưu giữ các tham chiếu đến tất cả các tài nguyên được chiếm bởi một quy trình và khi tôi "giết" một quy trình, Linux chỉ đơn giản đi qua bảng đó và giải phóng từng tài nguyên một?

Tôi thực sự muốn biết tất cả điều đó!



Câu trả lời:


72

Gửi kill -9 đến một quy trình không yêu cầu sự hợp tác của quy trình (như xử lý tín hiệu), nó sẽ giết chết nó.

Bạn cho rằng bởi vì một số tín hiệu có thể bị bắt và bỏ qua chúng đều liên quan đến sự hợp tác. Nhưng theo như man 2 signal, "các tín hiệu SIGKILL và SIGSTOP có thể bị bắt hoặc bỏ qua". SIGTERM có thể bị bắt, đó là lý do tại sao đồng bằng killkhông phải lúc nào cũng hiệu quả - nói chung điều này có nghĩa là một cái gì đó trong trình xử lý của quy trình đã bị sai lệch. 1

Nếu một quá trình không (hoặc không thể) xác định trình xử lý cho tín hiệu đã cho, thì kernel thực hiện một hành động mặc định. Trong trường hợp SIGTERM và SIGKILL, điều này là để chấm dứt quá trình (trừ khi PID của nó là 1; hạt nhân sẽ không chấm dứt init) 2 nghĩa là các tệp xử lý của nó bị đóng, bộ nhớ của nó được trả về nhóm hệ thống, cha mẹ của nó nhận SIGCHILD, mồ côi của nó trẻ em được thừa kế bởi init, v.v., giống như nó đã được gọi exit(xem man 2 exit). Quá trình không còn tồn tại - trừ khi nó kết thúc dưới dạng zombie, trong trường hợp đó, nó vẫn được liệt kê trong bảng quy trình của kernel với một số thông tin; Điều đó xảy ra khi cha mẹ của nó khôngwaitvà đối phó với thông tin này đúng cách. Tuy nhiên, các quá trình zombie không còn có bất kỳ bộ nhớ nào được phân bổ cho chúng và do đó không thể tiếp tục thực thi.

Có thứ gì đó giống như một bảng toàn cầu trong bộ nhớ trong đó Linux giữ các tham chiếu đến tất cả các tài nguyên được chiếm bởi một quy trình và khi tôi "giết" một quy trình, Linux chỉ đơn giản đi qua bảng đó và giải phóng từng tài nguyên một?

Tôi nghĩ đó là đủ chính xác. Bộ nhớ vật lý được theo dõi theo trang (một trang thường bằng một đoạn 4 KB) và các trang đó được lấy từ và trở về nhóm chung. Điều phức tạp hơn một chút là một số trang được giải phóng được lưu trong bộ nhớ cache trong trường hợp dữ liệu chứa trong đó được yêu cầu lại (nghĩa là dữ liệu được đọc từ một tệp vẫn còn tồn tại).

Các trang nói về "tín hiệu" nhưng chắc chắn đó chỉ là một sự trừu tượng.

Chắc chắn, tất cả các tín hiệu là một trừu tượng. Chúng là khái niệm, giống như "quy trình". Tôi đang chơi ngữ nghĩa một chút, nhưng nếu bạn muốn nói SIGKILL khác biệt về chất so với SIGTERM, thì có và không. Đúng theo nghĩa là nó không thể bị bắt, nhưng không có nghĩa là cả hai đều là tín hiệu. Theo một định nghĩa tương tự, một quả táo không phải là một quả cam mà là táo và cam, theo một định nghĩa định sẵn, cả hai quả. SIGKILL có vẻ trừu tượng hơn vì bạn không thể bắt được nó, nhưng nó vẫn là một tín hiệu. Đây là một ví dụ về xử lý SIGTERM, tôi chắc chắn bạn đã thấy những điều này trước đây:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void sighandler (int signum, siginfo_t *info, void *context) {
    fprintf (
        stderr,
        "Received %d from pid %u, uid %u.\n",
        info->si_signo,
        info->si_pid,
        info->si_uid
    );
}

int main (void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sighandler;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGTERM, &sa, NULL);
    while (1) sleep(10);
    return 0;
}

Quá trình này sẽ chỉ ngủ mãi mãi. Bạn có thể chạy nó trong một thiết bị đầu cuối và gửi SIGTERM với kill. Nó phun ra những thứ như:

Received 15 from pid 25331, uid 1066.

1066 là UID của tôi. Bộ vi xử lý sẽ là lớp vỏ mà từ đó killđược thực thi hoặc bộ lọc giết chết nếu bạn rẽ nhánh nó ( kill 25309 & echo $?).

Một lần nữa, không có điểm nào trong việc thiết lập trình xử lý cho SIGKILL vì nó không hoạt động. 3 Nếu tôi kill -9 25309quá trình sẽ chấm dứt. Nhưng đó vẫn là một tín hiệu; hạt nhân có thông tin về người đã gửi tín hiệu , loại tín hiệu đó là gì, v.v.


1. Nếu bạn chưa xem danh sách các tín hiệu có thể , hãy xem kill -l.

2. Một ngoại lệ khác, như Tim Post đề cập dưới đây, áp dụng cho các quá trình trong giấc ngủ không bị gián đoạn . Chúng không thể được đánh thức cho đến khi vấn đề cơ bản được giải quyết và do đó TẤT CẢ các tín hiệu (bao gồm cả SIGKILL) được hoãn lại trong suốt thời gian. Tuy nhiên, một quá trình không thể tạo ra tình huống đó theo mục đích.

3. Điều này không có nghĩa là sử dụng kill -9là một điều tốt hơn để làm trong thực tế. Trình xử lý ví dụ của tôi là một điều xấu theo nghĩa mà nó không dẫn đến exit(). Mục đích thực sự của một trình xử lý SIGTERM là tạo cơ hội cho quá trình thực hiện những việc như dọn sạch các tệp tạm thời, sau đó thoát ra một cách tự nguyện. Nếu bạn sử dụng kill -9, nó sẽ không có cơ hội này, vì vậy chỉ làm điều đó nếu phần "thoát tự nguyện" dường như đã thất bại.


Ok nhưng điều gì giết chết quá trình với -9vì đó là vấn đề thực sự làm thế nào người mong muốn cái này sẽ chết! ;)
Kiwy

@Kiwy: Nhân. IPC bao gồm các tín hiệu đi qua nó; kernel thực hiện các hành động mặc định.
goldilocks

12
Có thể đáng nói đến việc ngủ đĩa (D) trước tất cả các tín hiệu, trong khi quá trình ở trạng thái đó. Do đó, cố gắng để kill -9các quy trình ràng buộc I / O nhất định sẽ không hoạt động, ít nhất là không ngay lập tức.
Tim Post

7
Tôi đã thêm rằng vì kill -9không thể bị bắt, một quá trình nhận nó không thể thực hiện bất kỳ việc dọn dẹp nào (ví dụ: xóa các tệp tạm thời, giải phóng bộ nhớ dùng chung, v.v.) trước khi thoát. Do đó, chỉ sử dụng kill -9(aka kill -kill) như là phương sách cuối cùng. Bắt đầu với một kill -hupvà / hoặc kill -termđầu tiên và sau đó sử dụng kill -killnhư đòn cuối cùng.
JRFerguson

"Quá trình không còn tồn tại - trừ khi nó kết thúc dưới dạng zombie, trong trường hợp đó nó vẫn được liệt kê trong bảng quy trình của kernel với một số thông tin" thực sự, tất cả các quá trình đều chuyển sang trạng thái zombie khi chúng chết và zombie sẽ biến mất khi zombie chết cha mẹ chờ đợi đứa trẻ, thông thường điều đó xảy ra quá nhanh để bạn thấy điều đó xảy ra
thông minh

3

Mỗi tiến trình chạy trong thời gian dự kiến ​​và sau đó bị gián đoạn bởi bộ đếm thời gian phần cứng, để cung cấp lõi CPU cho các tác vụ khác. Đây là lý do tại sao có thể có nhiều tiến trình hơn so với lõi CPU hoặc thậm chí chạy tất cả hệ điều hành với nhiều quy trình trên một CPU lõi đơn.

Sau khi quá trình bị gián đoạn, điều khiển sẽ trở về mã kernel. Mã đó sau đó có thể đưa ra quyết định không tiếp tục thực hiện quy trình bị gián đoạn, mà không có bất kỳ sự hợp tác nào từ phía quy trình. kill -9 có thể kết thúc thực thi trong bất kỳ dòng nào trong chương trình của bạn.


0

Đây là một mô tả lý tưởng hóa về cách giết chết một quá trình hoạt động. Trong thực tế, bất kỳ biến thể Unix nào cũng sẽ có nhiều biến chứng và tối ưu hóa bổ sung.

Hạt nhân có cấu trúc dữ liệu cho mỗi quy trình lưu trữ thông tin về bộ nhớ mà nó ánh xạ, luồng nào và khi nào chúng được lên lịch, tệp nào đã mở, v.v. Nếu hạt nhân quyết định giết một tiến trình, nó sẽ ghi chú vào cấu trúc dữ liệu của quy trình (và có lẽ trong cấu trúc dữ liệu của từng luồng) mà quy trình sẽ bị hủy.

Nếu một trong các luồng của tiến trình hiện được lên lịch trên một CPU khác, hạt nhân có thể kích hoạt ngắt trên CPU khác đó để khiến luồng đó dừng thực thi nhanh hơn.

Khi bộ lập lịch thông báo rằng một luồng đang trong quá trình phải bị hủy, nó sẽ không lập lịch cho nó nữa.

Khi không có chủ đề nào của tiến trình được lên lịch nữa, kernel bắt đầu giải phóng các tài nguyên của tiến trình (bộ nhớ, mô tả tệp, lỗi). Mỗi khi kernel giải phóng một tài nguyên, nó sẽ kiểm tra xem chủ sở hữu của nó có còn tài nguyên sống hay không. Khi quy trình không còn tài nguyên trực tiếp (ánh xạ bộ nhớ, mô tả tệp mở, tập), cấu trúc dữ liệu cho chính quá trình có thể được giải phóng và có thể xóa mục nhập tương ứng khỏi bảng quy trình.

Một số tài nguyên có thể được giải phóng ngay lập tức (ví dụ: giải phóng bộ nhớ không được sử dụng bởi thao tác I / O). Các tài nguyên khác phải chờ, ví dụ: dữ liệu mô tả hoạt động I / O không thể được giải phóng trong khi hoạt động I / O đang diễn ra (trong khi DMA đang diễn ra, bộ nhớ mà nó đang truy cập đang sử dụng và hủy DMA yêu cầu tiếp xúc với ngoại vi). Trình điều khiển cho một tài nguyên như vậy được thông báo và có thể cố gắng nhanh chóng hủy bỏ; một khi hoạt động không còn trong tiến trình, trình điều khiển sẽ hoàn thành việc giải phóng tài nguyên đó.

(Mục trong bảng quy trình thực sự là một tài nguyên thuộc về quy trình cha, được giải phóng khi quy trình chết và cha mẹ thừa nhận sự kiện .)

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.