Xử lý tín hiệu với nhiều luồng trong Linux


119

Trong Linux, điều gì sẽ xảy ra khi một chương trình (có thể có nhiều luồng) nhận được một tín hiệu, như SIGTERM hoặc SIGHUP?

Chủ đề nào chặn tín hiệu? Nhiều luồng có thể nhận được cùng một tín hiệu không? Có một luồng đặc biệt hoàn toàn dành riêng cho việc xử lý tín hiệu không? Nếu không, điều gì xảy ra bên trong luồng xử lý tín hiệu? Làm thế nào để quá trình thực thi tiếp tục sau khi quy trình xử lý tín hiệu kết thúc?

Câu trả lời:


35

Điều này có một chút sắc thái, dựa trên phiên bản nhân Linux bạn đang sử dụng.

Giả sử 2,6 chuỗi posix và nếu bạn đang nói về việc HĐH gửi SIGTERM hoặc SIGHUP, tín hiệu sẽ được gửi tới quá trình, được nhận và xử lý bởi luồng gốc. Sử dụng các luồng POSIX, bạn cũng có thể gửi SIGTERM đến các luồng riêng lẻ, nhưng tôi nghi ngờ bạn đang hỏi về điều gì sẽ xảy ra khi Hệ điều hành gửi tín hiệu đến quy trình.

Trong 2.6, SIGTERM sẽ khiến các luồng con thoát ra "sạch sẽ", trong đó 2.4, các luồng con được để ở trạng thái không xác định.


Và điều gì xảy ra bên trong luồng gốc khi nhận được tín hiệu? Giả sử tôi đã viết một trình xử lý tín hiệu tùy chỉnh cho SIGUSR1 và bây giờ tôi đang gửi tín hiệu đó đến quy trình. Chủ đề gốc sẽ nhận được tín hiệu đó. Có thể nó đang ở giữa một số chức năng tại thời điểm đó. Chuyện gì đang xảy ra vậy?

1
nếu bạn có thiết lập trình xử lý, nó sẽ được coi là ngắt và luồng chương trình sẽ tạm dừng và trình xử lý tùy chỉnh của bạn sẽ được thực thi. Khi nó được thực thi, quyền điều khiển sẽ trở lại, giả sử bạn chưa làm gì để thay đổi luồng bình thường (thoát, v.v.).
Alan

Lưu ý rằng điều này dành riêng cho SIGUSR1, IIRC không làm gián đoạn các cuộc gọi hệ thống. Ví dụ: nếu bạn đã thử điều này với SIGINT, nó có thể làm gián đoạn quá trình đọc luồng và khi bạn quay lại đọc, luồng có thể trả về lỗi rằng nó đã bị gián đoạn.
Alan

10
Tôi hơi bối rối về ý nghĩa của "chủ đề gốc". Điều này có nghĩa là trình xử lý cho SIGTERM sẽ luôn chạy trong luồng chính hay nó có thể chạy trong bất kỳ luồng nào?
Stephen Nutt

3
Câu trả lời này , có thể nói rằng một chuỗi tùy ý được chọn để xử lý tín hiệu, mâu thuẫn với câu trả lời của bạn.
user202729

134

pthreads(7) mô tả rằng POSIX.1 yêu cầu tất cả các luồng trong một thuộc tính chia sẻ quy trình, bao gồm:

  • vị trí tín hiệu

POSIX.1 cũng yêu cầu một số thuộc tính phải khác biệt cho từng luồng, bao gồm:

Quy complete_signaltrình của nhân Linux có khối mã sau - các nhận xét khá hữu ích:

/*
 * Now find a thread we can wake up to take the signal off the queue.
 *
 * If the main thread wants the signal, it gets first crack.
 * Probably the least surprising to the average bear.
 */
if (wants_signal(sig, p))
        t = p;
else if (!group || thread_group_empty(p))
        /*
         * There is just one thread and it does not need to be woken.
         * It will dequeue unblocked signals before it runs again.
         */
        return;
else {
        /*
         * Otherwise try to find a suitable thread.
         */
        t = signal->curr_target;
        while (!wants_signal(sig, t)) {
                t = next_thread(t);
                if (t == signal->curr_target)
                        /*
                         * No thread needs to be woken.
                         * Any eligible threads will see
                         * the signal in the queue soon.
                         */
                        return;
        }
        signal->curr_target = t;
}

/*
 * Found a killable thread.  If the signal will be fatal,
 * then start taking the whole group down immediately.
 */
if (sig_fatal(p, sig) &&
    !(signal->flags & SIGNAL_GROUP_EXIT) &&
    !sigismember(&t->real_blocked, sig) &&
    (sig == SIGKILL || !p->ptrace)) {
        /*
         * This signal will be fatal to the whole group.
         */

Vì vậy, bạn thấy rằng bạn chịu trách nhiệm về nơi các tín hiệu được phân phối:

Nếu quy trình của bạn đã đặt vị trí của tín hiệu thành SIG_IGNhoặc SIG_DFL, thì tín hiệu đó sẽ bị bỏ qua (hoặc mặc định - kill, core, hoặc ignore) đối với tất cả các luồng.

Nếu quy trình của bạn đã thiết lập bố trí tín hiệu thành một quy trình xử lý cụ thể, thì bạn có thể kiểm soát luồng nào sẽ nhận tín hiệu bằng cách sử dụng mặt nạ tín hiệu luồng cụ thể pthread_sigmask(3). Bạn có thể chỉ định một luồng để quản lý tất cả chúng hoặc tạo một luồng cho mỗi tín hiệu, hoặc bất kỳ hỗn hợp nào của các tùy chọn này cho các tín hiệu cụ thể hoặc bạn dựa vào hành vi mặc định hiện tại của nhân Linux là phân phối tín hiệu đến luồng chính.

Tuy nhiên, một số tín hiệu đặc biệt theo signal(7)trang người đàn ông:

Một tín hiệu có thể được tạo (và do đó đang chờ xử lý) cho toàn bộ quá trình (ví dụ: khi được gửi bằng kill (2) ) hoặc cho một chuỗi cụ thể (ví dụ: một số tín hiệu nhất định, chẳng hạn như SIGSEGV và SIGFPE, được tạo ra như một hệ quả của việc thực thi một hướng dẫn ngôn ngữ máy cụ thể được hướng tới luồng, cũng như các tín hiệu được nhắm mục tiêu đến một luồng cụ thể bằng cách sử dụng pthread_kill (3) ). Một tín hiệu hướng quá trình có thể được gửi đến bất kỳ một trong các luồng hiện không bị chặn tín hiệu. Nếu nhiều hơn một trong các luồng có tín hiệu được bỏ chặn, thì hạt nhân sẽ chọn một luồng tùy ý để phân phối tín hiệu.

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.