The Linux Programming Interface Chương 20 · Signals: Fundamental Concepts

Chương 20 · Tín hiệu — khái niệm nền tảng Chapter 20 · Signals: Fundamental Concepts

Tín hiệu là gì, và một tiến trình phản ứng với nó ra sao Tín hiệu là gì, và máy làm gì khi nhận được một tín hiệu What a signal is, and how a process responds to one

Kerrisk định nghĩa tín hiệu (signal) là một thông báo gửi tới tiến trình rằng một sự kiện đã xảy ra — đôi khi được gọi là "ngắt phần mềm" (software interrupt), vì nó cắt ngang luồng chạy bình thường và thường không thể đoán trước thời điểm đến.12 Chương đi qua: khái niệm và vòng đời của tín hiệu; các loại tín hiệu chuẩn (1–31) cùng hành động mặc định của chúng; cách đổi disposition bằng signal() và sigaction() (được ưu tiên); cách gửi tín hiệu với kill(), raise(), killpg(); cách kiểm tra một tiến trình còn tồn tại; quyền cần có để gửi; cách mô tả tín hiệu với strsignal()/psignal(); tập tín hiệu, mặt nạ tín hiệu và việc chặn (sigprocmask); tín hiệu đang chờ (sigpending); việc tín hiệu chuẩn không xếp hàng; và chờ tín hiệu bằng pause().1

Hãy tưởng tượng bạn đang làm việc thì có người gõ cửa. Tiếng gõ ấy là một tín hiệu: nó báo "có chuyện xảy ra" và cắt ngang việc bạn đang làm. Tiến trình trong máy cũng vậy — nó có thể bị một tín hiệu gõ cửa bất cứ lúc nào, và nó phải quyết định: lờ đi, làm theo mặc định, hay chạy một việc riêng để đáp lại. Chương này dạy cách nhận biết, gửi đi, chặn lại và chờ đợi những tiếng gõ cửa ấy. Nhớ nhé: tín hiệu là một tiếng gõ cửa báo "có chuyện" cho tiến trình.

Kerrisk defines a signal as a notification to a process that an event has occurred — sometimes called a "software interrupt," because it cuts across a program's normal flow and usually cannot be predicted in advance.12 The chapter walks through: the concept and lifecycle of signals; the standard signal types (1–31) and their default actions; changing a signal's disposition with signal() and the preferred sigaction(); sending signals with kill(), raise(), killpg(); checking whether a process still exists; the permissions required to send; describing signals with strsignal()/psignal(); signal sets, the signal mask and blocking (sigprocmask); pending signals (sigpending); the fact that standard signals are not queued; and waiting for a signal with pause().1

Góc nhìn của nhân: một tín hiệu đi qua một vòng đời rõ rệt. Nó được sinh ra (generated) khi một sự kiện xảy ra — do nhân, do một tiến trình khác, hay do phần cứng; rồi nó được phát (delivered) tới tiến trình đích, lúc đó tiến trình thực hiện hành động tương ứng với disposition hiện thời của tín hiệu. Khoảng giữa sinh và phát, tín hiệu ở trạng thái đang chờ (pending); và nếu tín hiệu đó đang bị chặn (blocked) bởi mặt nạ tín hiệu của tiến trình, nó cứ nằm chờ cho tới khi được bỏ chặn.12The kernel's view: a signal passes through a distinct lifecycle. It is generated when an event occurs — by the kernel, by another process, or by hardware; then it is delivered to the target process, at which point the process performs the action dictated by the signal's current disposition. Between generation and delivery the signal is pending; and if that signal is currently blocked by the process's signal mask, it remains pending until it is unblocked.12

Luận đề của chương: một tín hiệu là một cơ chế thông báo bất đồng bộ do nhân điều phối; làm chủ được nó nghĩa là làm chủ bốn việc — phân loại các tín hiệu và biết hành động mặc định của chúng, đổi cách tiến trình phản ứng (disposition) một cách khả chuyển qua sigaction(), gửi tín hiệu đúng quyền với kill(), và trì hoãn việc phát bằng mặt nạ tín hiệu — tất cả trong khi nhớ rằng tín hiệu chuẩn không xếp hàng.1

Ý chính: tín hiệu là tiếng gõ cửa bất ngờ từ bên ngoài luồng chạy của chương trình. Hiểu một tín hiệu là biết: nó mặc định làm gì, làm sao đổi cách đáp lại, làm sao tự gửi, và làm sao bảo "khoan đã" để nó chờ. Nhớ nhé: tín hiệu là tiếng gõ cửa — bạn chọn cách mở, cách gửi, và cách bảo nó đợi.

The chapter's thesis: a signal is an asynchronous notification mechanism orchestrated by the kernel; mastering it means mastering four things — classifying signals and knowing their default actions, changing how a process reacts (its disposition) portably via sigaction(), sending signals with the right permissions via kill(), and deferring delivery with the signal mask — all while remembering that standard signals are not queued.1

Cụm khái niệm 01 Concept cluster 01

Một tín hiệu đi qua vòng đời sinh–chờ–phát — và mỗi loại có một hành động mặc định Tín hiệu sinh ra, chờ, rồi được trao — và mỗi loại có cách xử mặc định A signal has a generate–pending–deliver lifecycle — and each type has a default action

Mỗi tín hiệu là một số nguyên nhỏ, định danh duy nhất, bắt đầu tuần tự từ 1, với tên biểu tượng dạng SIGxxxx định nghĩa trong <signal.h>.1213 Vì con số thực tế khác nhau giữa các hệ, mã nên dùng tên chứ không dùng số. Trên Linux, các tín hiệu chuẩn (standard) được đánh số từ 1 đến 31; còn một tập khác là tín hiệu thời gian thực (realtime) — chương này mô tả tín hiệu chuẩn.1 Khi một tín hiệu đến mà tiến trình chưa thiết lập cách xử riêng, nhân thực thi một trong các hành động mặc định: kết thúc tiến trình (term), kết thúc kèm dump lõi (core), bỏ qua (ign), dừng (stop), hoặc tiếp tục chạy (cont).12

Mỗi loại tín hiệu có một con số và một cái tên, ví dụ SIGINT là tín hiệu ngắt khi bạn bấm Ctrl-C. Có khoảng ba mươi loại tín hiệu "cổ điển" (số 1 đến 31). Khi một tín hiệu tới mà bạn chưa dặn gì, máy làm theo "nếp mặc định" của loại đó: có loại khiến chương trình tắt ngay, có loại bị lờ đi, có loại khiến chương trình tạm dừng rồi chạy tiếp. Nhớ nhé: mỗi tín hiệu có tên, có số, và có một nếp xử mặc định nếu bạn không dặn gì khác.

Each signal is a unique small integer, numbered sequentially from 1, with a symbolic name of the form SIGxxxx defined in <signal.h>.1213 Because the actual numbers vary across implementations, code should use the names, not the numbers. On Linux the standard signals are numbered 1 to 31; a separate set are the realtime signals — this chapter covers the standard ones.1 When a signal arrives and the process has set no handling of its own, the kernel performs one of the default actions: terminate the process (term), terminate with a core dump (core), ignore (ign), stop (stop), or resume (cont).12

Định nghĩa. Disposition của một tín hiệu là cái mà tiến trình quy định sẽ xảy ra khi tín hiệu đó được phát: để nguyên hành động mặc định, bỏ qua tín hiệu (SIG_IGN), hoặc chạy một trình xử lý tín hiệu (signal handler) do lập trình viên viết (SIG_DFL là khôi phục về mặc định).13

Nói đơn giản. "Disposition" là lời dặn trước: khi tín hiệu này tới thì làm gì — cứ làm mặc định, lờ nó đi, hay chạy một đoạn việc riêng của tôi. Nhớ nhé: disposition là lời dặn "khi gõ cửa thì làm thế này".

Definition. A signal's disposition is what the process specifies should happen when that signal is delivered: leave the default action, ignore the signal (SIG_IGN), or run a programmer-written signal handler (SIG_DFL restores the default).13

sự kiện xảy ra
   ↓ generated
[ đang chờ · pending ] ← bị chặn? (mask)
   ↓ delivered
hành động = term | core | ign | stop | cont
        hoặc handler / SIG_IGN

có chuyện xảy ra
  → sinh tín hiệu
  → (có thể phải chờ)
  → được trao → làm gì đó
(tắt / lờ / chạy việc riêng)

event occurs
   ↓ generated
[ pending ] ← blocked? (mask)
   ↓ delivered
action = term | core | ign | stop | cont
        or handler / SIG_IGN

  1. software interrupt

    Tín hiệu là bất đồng bộ — không đoán được lúc đến Tín hiệu đến bất chợt, không báo trước Signals are asynchronous — you can't predict their arrival

    Tín hiệu giống ngắt phần cứng ở chỗ nó cắt ngang luồng chạy bình thường; trong hầu hết trường hợp không thể biết trước chính xác khi nào một tín hiệu sẽ tới.1 Các nguồn sinh tín hiệu điển hình gồm: một ngoại lệ phần cứng (ví dụ chia cho 0 hay tham chiếu địa chỉ sai), người dùng gõ một ký tự đặc biệt trên terminal (như Ctrl-C sinh SIGINT), hay một tiến trình khác chủ động gửi.12

    Tín hiệu cắt ngang việc chương trình đang làm, giống như chuông điện thoại reo giữa bữa ăn. Nó có thể do một lỗi phần cứng (chia cho 0), do bạn bấm một phím đặc biệt (Ctrl-C), hay do một chương trình khác gửi sang. Nhớ nhé: tín hiệu đến bất chợt, giữa chừng việc đang làm.

    A signal resembles a hardware interrupt in that it interrupts normal flow; in most cases you cannot know in advance exactly when one will arrive.1 Typical sources are: a hardware exception (such as dividing by zero or a bad memory reference), the user typing a terminal special character (like Ctrl-C generating SIGINT), or another process deliberately sending one.12

    Bạn gặp nó ở đâuWhere you meet it Mỗi khi bạn bấm Ctrl-C để dừng một lệnh đang chạy ở terminal — đó là SIGINT được sinh và phát tới tiến trình tiền cảnh.2 Khi bấm Ctrl-C để dừng một lệnh — đó chính là một tín hiệu đang được gửi đi. Every time you press Ctrl-C to stop a running command in the terminal — that is SIGINT being generated and delivered to the foreground process.2

    Chuẩn và thời gian thựcStandard vs realtime

    Tín hiệu rơi vào hai nhóm: nhóm chuẩn (số 1–31 trên Linux), dùng để nhân báo các sự kiện truyền thống; và nhóm thời gian thực được thêm sau, có nhiều điểm khác — đáng chú ý nhất là tín hiệu thời gian thực được xếp hàng, còn tín hiệu chuẩn thì không.12Signals fall into two groups: the standard set (numbered 1–31 on Linux), used by the kernel for traditional events; and the later realtime set, which differs in several ways — most notably, realtime signals are queued, whereas standard signals are not.12

    Dùng tên, đừng dùng sốUse names, not numbers

    Vì số hiệu của mỗi tín hiệu khác nhau giữa các hiện thực UNIX, chương trình khả chuyển luôn tham chiếu tín hiệu bằng tên biểu tượng (SIGINT, SIGTERM…) chứ không bằng số thô.1Because each signal's number varies across UNIX implementations, portable programs always refer to signals by their symbolic names (SIGINT, SIGTERM…) rather than raw numbers.1

  2. default actions

    Năm hành động mặc định: term, core, ign, stop, cont Năm nếp xử mặc định khi không dặn gì Five default actions: term, core, ign, stop, cont

    Mỗi tín hiệu chuẩn có một hành động mặc định ghi trong bảng của sách (Table 20-1): term — kết thúc tiến trình; core — kết thúc và ghi một tệp core dump để gỡ lỗi; ign — tín hiệu bị bỏ qua; stop — tạm dừng tiến trình; cont — cho tiến trình đang dừng chạy tiếp.12 Phần lớn tín hiệu trên các bản UNIX đời đầu có mặc định là kết thúc tiến trình.1

    Khi một tín hiệu tới mà bạn chưa dặn gì, máy làm theo một trong năm nếp: tắt chương trình; tắt kèm ghi "hộp đen" (core dump) để soi lỗi; lờ đi; tạm dừng; hoặc cho chạy tiếp. Mỗi loại tín hiệu có sẵn một nếp riêng. Nhớ nhé: không dặn gì thì máy theo nếp mặc định của loại tín hiệu đó.

    Each standard signal has a default action listed in the book's table (Table 20-1): term — terminate the process; core — terminate and write a core dump file for debugging; ign — the signal is ignored; stop — suspend the process; cont — resume a stopped process.12 On early UNIX systems most signals defaulted to terminating the process.1

    Bạn gặp nó ở đâuWhere you meet it Khi một chương trình "chết" và để lại tệp core — đó là một tín hiệu có mặc định core (như SIGSEGV, SIGABRT) vừa kết thúc nó.2 Khi chương trình sập và để lại tệp core — đó là một tín hiệu loại "tắt kèm hộp đen". When a program dies and leaves a core file — a signal whose default is core (like SIGSEGV, SIGABRT) just terminated it.2
  3. SIGKILL / SIGSTOP

    Hai tín hiệu không thể bắt, chặn hay bỏ qua Hai tín hiệu không ai cưỡng lại được Two signals that cannot be caught, blocked, or ignored

    SIGKILL (kết thúc tức thì) và SIGSTOP (dừng tức thì) là hai ngoại lệ đặc biệt: disposition của chúng không thể bị thay đổi — không thể bắt bằng handler, không thể bỏ qua, và cũng không thể chặn.12 Đó là lý do kill -9 (gửi SIGKILL) luôn hạ được một tiến trình bướng bỉnh — nó bỏ qua mọi handler. Vì vậy Kerrisk khuyên: hãy thử SIGTERM trước (tiến trình có thể dọn dẹp rồi thoát gọn), chỉ dùng SIGKILL như biện pháp cuối cùng.1

    Hầu hết tín hiệu, chương trình có quyền "chặn cửa" hoặc tự xử theo ý mình. Nhưng hai tín hiệu đặc biệt — "giết" (SIGKILL) và "dừng" (SIGSTOP) — thì không ai cưỡng lại được: không chặn được, không lờ được, không tự xử được. Nên kill -9 luôn hạ được chương trình. Nhưng hãy lịch sự thử SIGTERM trước để nó kịp dọn dẹp. Nhớ nhé: SIGKILL và SIGSTOP là tuyệt đối — dùng SIGKILL là lựa chọn cuối.

    SIGKILL (instant termination) and SIGSTOP (instant stop) are special exceptions: their disposition cannot be changed — they cannot be caught by a handler, ignored, or blocked.12 That is why kill -9 (sending SIGKILL) always brings down a stubborn process — it bypasses every handler. Hence Kerrisk's advice: try SIGTERM first (the process can clean up and exit gracefully), and reserve SIGKILL as a last resort.1

    Bạn gặp nó ở đâuWhere you meet it Khi một tiến trình "treo" không chịu thoát dù đã gửi SIGTERM — kill -KILL là cứu cánh, vì nó không thể bị handler nào cản.1 Khi một chương trình treo cứng không chịu tắt — kill -9 luôn dứt điểm được. When a hung process refuses to exit even after SIGTERM — kill -KILL is the fallback, since no handler can block it.1

    Vì sao phải cứng rắn như vậyWhy they must be absolute

    Nếu một tiến trình có thể bắt hay bỏ qua SIGKILL, nó sẽ có thể tự biến mình thành bất tử — không cách nào hạ được. Giữ SIGKILL và SIGSTOP ngoài tầm với của tiến trình đảm bảo hệ điều hành luôn còn một cách dứt khoát để kết thúc hay tạm dừng bất kỳ tiến trình nào.12If a process could catch or ignore SIGKILL, it could make itself immortal — unkillable. Keeping SIGKILL and SIGSTOP out of a process's reach guarantees the operating system always retains a definitive way to terminate or suspend any process.12

    SIGTERM trước, SIGKILL sauSIGTERM first, SIGKILL after

    SIGTERM là tín hiệu "xin hãy kết thúc" tiêu chuẩn mà công cụ như kill gửi mặc định; tiến trình có thể bắt nó để lưu dữ liệu và đóng tài nguyên. Nhảy thẳng sang SIGKILL bỏ qua bước dọn dẹp ấy, nên dễ để lại trạng thái dở dang.1SIGTERM is the standard "please terminate" signal that tools like kill send by default; a process can catch it to save data and close resources. Jumping straight to SIGKILL skips that cleanup, so it can leave state half-finished.1

Cụm khái niệm 02 Concept cluster 02

Đổi disposition: signal() là bản gốc, sigaction() là bản được ưu tiên Hai cách dặn máy phản ứng với tín hiệu — và cách nên dùng Changing disposition: signal() is the original, sigaction() the preferred one

UNIX cho hai cách đổi disposition của một tín hiệu: signal() và sigaction().1 signal() là API gốc, giao diện đơn giản hơn, nhưng hành vi của nó khác nhau giữa các hệ UNIX — một nguồn lỗi khả chuyển kinh điển. Vì vậy Kerrisk khuyến nghị (rất mạnh) dùng sigaction() để thiết lập trình xử lý tín hiệu trong chương trình khả chuyển.134 Thú vị là trên glibc, bản thân signal() được hiện thực như một hàm thư viện đặt chồng lên sigaction().1

Có hai cách dặn máy "khi tín hiệu này tới thì làm thế này". Cách cũ tên signal() ngắn gọn nhưng trớ trêu: mỗi hệ UNIX hiểu nó một kiểu, nên dễ gây lỗi khi chuyển máy. Cách mới tên sigaction() dài hơn chút nhưng rõ ràng và giống nhau ở mọi nơi. Nhớ nhé: để dặn tín hiệu cho chắc và chuyển máy an toàn, hãy dùng sigaction().

UNIX provides two ways to change a signal's disposition: signal() and sigaction().1 signal() is the original API with a simpler interface, but its behavior varies across UNIX systems — a classic portability pitfall. So Kerrisk (strongly) recommends sigaction() for establishing signal handlers in portable programs.134 Interestingly, on glibc signal() itself is implemented as a library function layered on top of sigaction().1

Định nghĩa. signal(sig, handler) đặt disposition cho tín hiệu sig; đối số handler là địa chỉ hàm xử lý, hoặc một trong hai hằng đặc biệt: SIG_DFL (khôi phục hành động mặc định) và SIG_IGN (bỏ qua tín hiệu). Hàm trả về disposition trước đó khi thành công, hoặc SIG_ERR khi lỗi.13

Nói đơn giản. signal(tín_hiệu, việc_cần_làm): bạn nói "khi tín hiệu này tới, chạy đoạn việc này", hoặc đưa SIG_DFL để trả về mặc định, SIG_IGN để lờ đi. Nhớ nhé: SIG_DFL là về mặc định, SIG_IGN là lờ đi.

Definition. signal(sig, handler) sets the disposition for signal sig; the handler argument is a handler function's address, or one of two special constants: SIG_DFL (restore the default action) and SIG_IGN (ignore the signal). It returns the previous disposition on success, or SIG_ERR on error.13

#include <signal.h>
void (*signal(int sig,
  void (*handler)(int)))(int);
/* trả về disposition cũ, hoặc SIG_ERR */
signal(SIGINT, SIG_IGN); /* lờ Ctrl-C */

signal(SIGINT, SIG_IGN);
  --> lờ Ctrl-C
signal(SIGINT, SIG_DFL);
  --> về lại mặc định

#include <signal.h>
void (*signal(int sig,
  void (*handler)(int)))(int);
/* returns old disposition, or SIG_ERR */
signal(SIGINT, SIG_IGN); /* ignore Ctrl-C */

  1. sigaction()

    Vì sao sigaction() được ưu tiên tuyệt đối Vì sao nên chọn sigaction() thay vì signal() Why sigaction() is strongly preferred

    sigaction() cung cấp chức năng mà signal() không có, và — quan trọng hơn — cho một hành vi nhất quán, xác định rõ khi thiết lập handler, không phụ thuộc hệ.14 Nó nhận một cấu trúc struct sigaction mô tả handler cùng các cờ (flags) và một tập tín hiệu sẽ được chặn trong lúc handler chạy. Vì những khác biệt khả chuyển của signal(), sách dùng sigaction() xuyên suốt các chương về sau.1

    sigaction() làm mọi việc signal() làm, nhưng rõ ràng và giống nhau ở mọi hệ máy, lại cho thêm nhiều lựa chọn tinh chỉnh. Vì thế đây là cách sách dùng từ đây về sau. Nhớ nhé: sigaction() là lựa chọn mặc định đúng đắn để bắt tín hiệu.

    sigaction() provides functionality that signal() lacks and — more importantly — gives consistent, well-defined behavior when establishing a handler, independent of the system.14 It takes a struct sigaction describing the handler, plus flags and a set of signals to block while the handler runs. Because of signal()'s portability differences, the book uses sigaction() throughout the later chapters.1

    Bạn gặp nó ở đâuWhere you meet it Trong gần như mọi chương trình nghiêm túc có bắt tín hiệu — và trong toàn bộ Chương 21 về thiết kế trình xử lý tín hiệu.4 Trong hầu hết chương trình thực tế có xử lý tín hiệu — và suốt Chương 21 kế tiếp. In nearly every serious program that catches signals — and throughout Chapter 21 on designing signal handlers.4

    signal() đặt trên sigaction()signal() layered on sigaction()

    Dù signal() được ghi trong mục 2 của man page (system call), trên glibc nó thực chất là một hàm thư viện gọi tới sigaction(). Chính vì vậy hành vi khả chuyển của signal() phụ thuộc vào cách thư viện C ánh xạ nó — một lý do nữa để gọi thẳng sigaction().13Although signal() is documented in section 2 of the manual (system calls), on glibc it is actually a library function that calls sigaction(). That is exactly why signal()'s portable behavior depends on how the C library maps it — one more reason to call sigaction() directly.13

    Đọc disposition mà không đổiReading disposition without changing it

    Không thể dùng signal() để chỉ đọc disposition hiện thời mà không đồng thời thay đổi nó; muốn chỉ xem (hoặc xem rồi đặt) thì phải dùng sigaction(), vốn tách bạch hai việc này.14You cannot use signal() merely to read the current disposition without also changing it; to just inspect it (or inspect-then-set) you must use sigaction(), which separates those operations.14

  2. SIG_IGN

    Bỏ qua khác với chặn — một phân biệt quan trọng "Lờ đi" không giống "cho chờ" Ignoring is not blocking — an important distinction

    Đặt disposition thành SIG_IGN khiến tín hiệu bị vứt bỏ khi được phát — nó đến rồi biến mất, không để lại dấu vết. Điều này khác hẳn việc chặn (block) qua mặt nạ tín hiệu: tín hiệu bị chặn không bị vứt, mà được giữ lại ở trạng thái đang chờ cho tới khi được bỏ chặn.12

    "Lờ đi" (SIG_IGN) nghĩa là tín hiệu tới thì bị ném đi luôn, coi như chưa từng có. "Cho chờ" (chặn) thì khác: tín hiệu đứng đợi ngoài cửa, chưa bị bỏ, và sẽ vào khi bạn mở chặn. Nhớ nhé: lờ đi là ném bỏ, chặn là bắt đứng chờ.

    Setting the disposition to SIG_IGN causes the signal to be discarded on delivery — it arrives and vanishes, leaving no trace. This differs entirely from blocking via the signal mask: a blocked signal is not discarded but held in the pending state until it is unblocked.12

    Bạn gặp nó ở đâuWhere you meet it Khi một chương trình muốn phớt lờ SIGINT trong một đoạn tới hạn — đặt SIG_IGN thì Ctrl-C hoàn toàn vô hại trong đoạn đó.3 Khi muốn Ctrl-C không có tác dụng trong một lúc — đặt tín hiệu đó thành "lờ đi". When a program wants to ignore SIGINT during a critical section — setting SIG_IGN makes Ctrl-C harmless there.3
  3. disposition is inherited

    Hai tín hiệu vẫn trơ: SIGKILL và SIGSTOP Dù muốn, bạn vẫn không đổi được hai tín hiệu này Two still immovable: SIGKILL and SIGSTOP

    Cả signal() lẫn sigaction() đều không thể đổi disposition của SIGKILL hay SIGSTOP — nỗ lực làm vậy sẽ thất bại.14 Đây là hệ quả trực tiếp của tính "không thể bắt, chặn hay bỏ qua" đã nêu ở cụm trước: không có cửa sau nào, dù qua API cũ hay mới.1

    Dù dùng cách cũ hay cách mới, bạn vẫn không thể dặn lại hai tín hiệu "giết" và "dừng". Chúng luôn làm đúng việc của mình. Nhớ nhé: không API nào đổi được SIGKILL và SIGSTOP.

    Neither signal() nor sigaction() can change the disposition of SIGKILL or SIGSTOP — any such attempt fails.14 This follows directly from their "cannot be caught, blocked, or ignored" property from the previous cluster: there is no back door, old API or new.1

    Bạn gặp nó ở đâuWhere you meet it Khi gọi sigaction(SIGKILL, …) trả về lỗi EINVAL — đó không phải bug, mà là thiết kế có chủ ý.4 Khi thử dặn lại SIGKILL và nhận lỗi — đó là điều bình thường, cố ý như vậy. When sigaction(SIGKILL, …) returns EINVAL — that is not a bug but a deliberate design.4

Cụm khái niệm 03 Concept cluster 03

Gửi tín hiệu: kill(), raise(), killpg() — và câu chuyện quyền hạn Cách tự gửi tín hiệu, và ai được phép gửi cho ai Sending signals: kill(), raise(), killpg() — and the question of permission

Một tiến trình (nếu đủ quyền) có thể gửi tín hiệu tới tiến trình khác bằng kill(pid, sig) — tên "kill" là di sản lịch sử, vì mặc định của phần lớn tín hiệu đời đầu là kết thúc tiến trình.15 Để tự gửi cho chính mình có raise(sig); để gửi cho cả một nhóm tiến trình (process group) có killpg().167 Việc gửi bị ràng buộc bởi quyền: nói đại thể, một tiến trình thường chỉ được gửi tín hiệu tới tiến trình khác nếu người gửi và người nhận thuộc cùng chủ sở hữu (hoặc người gửi là đặc quyền).15

Để gửi một tín hiệu cho chương trình khác, ta dùng kill() — cái tên nghe đáng sợ nhưng chỉ là "gửi tín hiệu", không nhất thiết là giết. Muốn gửi cho chính mình thì dùng raise(); gửi cho cả một nhóm thì dùng killpg(). Nhưng không phải ai cũng gửi được cho ai: thường chỉ gửi được cho tiến trình cùng chủ, trừ khi bạn là quản trị. Nhớ nhé: kill() là "gửi tín hiệu", và phải đúng quyền mới gửi được.

A process (with suitable permission) can send a signal to another with kill(pid, sig) — the name "kill" is a historical legacy, since most early signals defaulted to terminating the process.15 To send to itself there is raise(sig); to send to a whole process group there is killpg().167 Sending is bound by permission: broadly, an ordinary process may signal another only if sender and recipient share an owner (or the sender is privileged).15

Định nghĩa. kill(pid, sig) gửi tín hiệu sig tới tiến trình có số pid. Giá trị pid còn có các nghĩa đặc biệt: pid > 0 gửi tới một tiến trình; pid == 0 gửi tới mọi thành viên cùng nhóm tiến trình với người gửi; pid == -1 gửi tới mọi tiến trình người gửi có quyền gửi; pid < -1 gửi tới nhóm tiến trình có mã bằng trị tuyệt đối của pid.15

Nói đơn giản. kill(ai, tín_hiệu): gửi tín hiệu cho một tiến trình (theo số của nó). Có mẹo: số 0 nghĩa là "cả nhóm của tôi", số −1 nghĩa là "mọi tiến trình tôi được phép". Nhớ nhé: kill nhận một số pid, và vài giá trị đặc biệt mở rộng phạm vi gửi.

Definition. kill(pid, sig) sends signal sig to the process numbered pid. The pid value carries special meanings: pid > 0 targets one process; pid == 0 targets every member of the sender's own process group; pid == -1 targets every process the sender may signal; pid < -1 targets the process group whose ID equals the absolute value of pid.15

#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig);    /* gửi cho chính mình */
int killpg(pid_t pgrp, int sig);
kill(pid, 0); /* tín hiệu null: kiểm tra tồn tại */

kill(pid, sig) --> gửi cho pid
raise(sig)   --> gửi cho mình
kill(pid, 0) --> pid còn sống không?

#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig);    /* send to self */
int killpg(pid_t pgrp, int sig);
kill(pid, 0); /* null signal: existence check */

  1. null signal (0)

    Gửi tín hiệu 0 không gửi gì — nó kiểm tra sự tồn tại Tín hiệu số 0: không gửi gì, chỉ để dò xem còn sống không Signal 0 sends nothing — it checks existence

    Nếu sig là 0 (gọi là tín hiệu null), kill() không thực sự gửi tín hiệu nào; thay vào đó nó chỉ làm các bước kiểm tra lỗi thường lệ.15 Nhờ đó ta dùng tín hiệu null để kiểm tra một tiến trình có tồn tại không: nếu gọi thất bại với lỗi ESRCH, tiến trình đó không tồn tại; nếu thất bại với EPERM thì nó tồn tại nhưng ta không có quyền gửi.15

    Gửi "tín hiệu số 0" là một mẹo: thực ra chẳng gửi gì cả, chỉ để hỏi "tiến trình kia còn sống không?". Nếu máy báo lỗi "không tìm thấy" (ESRCH), nghĩa là nó đã biến mất. Nhớ nhé: tín hiệu 0 là cách gõ cửa hỏi "có ai ở nhà không" mà không làm phiền.

    If sig is 0 (the null signal), kill() sends no signal; instead it performs only the usual error checks.15 This lets you use the null signal to test whether a process exists: if the call fails with ESRCH, the process does not exist; if it fails with EPERM, it exists but you lack permission to signal it.15

    Bạn gặp nó ở đâuWhere you meet it Trong các script giám sát viết bằng C: định kỳ gọi kill(pid, 0) để biết một dịch vụ còn chạy hay đã chết.5 Khi muốn biết một tiến trình còn chạy không — kill(pid, 0) trả lời mà không làm phiền nó. In monitoring code: periodically call kill(pid, 0) to learn whether a service is still running or has died.5

    Một cuộc đua ẩnA hidden race

    Kiểm tra tồn tại bằng tín hiệu null có một bẫy: PID được tái sử dụng (Chương 6), nên tiến trình trả về "còn sống" có thể là một tiến trình khác vừa nhận lại PID cũ. Vì vậy cách này chỉ nên dùng khi biết PID chưa thể bị tái dùng trong khoảng thời gian liên quan.15Existence checks via the null signal have a trap: PIDs are reused (Chapter 6), so a process reported "alive" might be a different process that just inherited the old PID. Use this only when you know the PID cannot be recycled within the relevant window.15

    Các mã lỗi cần phân biệtError codes to distinguish

    ESRCH ("no such process") nghĩa là không có tiến trình với PID đó; EPERM nghĩa là có nhưng thiếu quyền. Phân biệt hai mã này cho phép suy ra tiến trình tồn tại ngay cả khi không gửi được tín hiệu thật.15ESRCH ("no such process") means no process with that PID exists; EPERM means one exists but you lack permission. Distinguishing the two lets you infer existence even when you cannot actually deliver a signal.15

  2. permission

    Không phải ai cũng gửi được cho ai Phải đúng quyền mới gửi tín hiệu được Not everyone may signal everyone

    Để gửi tín hiệu, tiến trình gửi phải có quyền phù hợp. Quy tắc cốt lõi: một tiến trình đặc quyền (CAP_KILL) gửi được cho bất kỳ ai; một tiến trình thường chỉ gửi được nếu định danh người dùng thực hoặc hiệu lực của nó khớp với định danh thực hoặc đã-lưu của tiến trình đích.15 Riêng SIGCONT có ngoại lệ: có thể gửi tới bất kỳ tiến trình nào trong cùng một phiên (session).1

    Hệ điều hành không cho bạn gửi tín hiệu lung tung tới chương trình của người khác — đó là chuyện bảo mật. Nói nôm na: bạn gửi được cho các chương trình của chính mình; chỉ "người quản trị" mới gửi được cho tất cả. Nhớ nhé: thường thì chỉ gửi tín hiệu được cho tiến trình cùng chủ với mình.

    To send a signal, the sender must have appropriate permission. The core rule: a privileged process (CAP_KILL) may signal anyone; an ordinary process may signal only if its real or effective user ID matches the real or saved user ID of the target.15 SIGCONT is a special case: it may be sent to any process in the same session.1

    Bạn gặp nó ở đâuWhere you meet it Khi lệnh kill báo "Operation not permitted" (EPERM) lúc bạn cố dừng tiến trình của người dùng khác mà không có quyền root.5 Khi bạn cố dừng chương trình của người khác và máy báo "không được phép" — đó là quy tắc quyền hạn. When kill reports "Operation not permitted" (EPERM) as you try to stop another user's process without root.5
  3. strsignal / psignal

    Mô tả tín hiệu bằng lời người đọc được Đổi số tín hiệu thành câu chữ dễ hiểu Describing a signal in human-readable words

    strsignal(sig) trả về một chuỗi mô tả tín hiệu (ví dụ "Interrupt"); nó có kiểm tra biên và nhạy locale, nên mô tả hiển thị theo ngôn ngữ hệ thống.18 psignal(sig, msg) in ra lỗi chuẩn (stderr) chuỗi msg, dấu hai chấm, rồi phần mô tả tín hiệu. Cả hai được ưa dùng hơn việc truy cập trực tiếp mảng sys_siglist.18

    Con số tín hiệu khó nhớ. strsignal() đổi một số tín hiệu thành câu chữ dễ hiểu như "Interrupt". psignal() thì in thẳng dòng mô tả ra màn hình lỗi. Nhớ nhé: strsignal cho bạn tên mô tả, psignal in nó ra giúp bạn.

    strsignal(sig) returns a string describing the signal (e.g. "Interrupt"); it does bounds checking and is locale-sensitive, so the description appears in the system's language.18 psignal(sig, msg) prints to standard error the string msg, a colon, then the signal description. Both are preferable to directly accessing the sys_siglist array.18

    Bạn gặp nó ở đâuWhere you meet it Trong thông báo lỗi thân thiện: thay vì in "caught signal 2", chương trình in "caught signal: Interrupt" nhờ strsignal().8 Khi một chương trình báo "bị tín hiệu: Interrupt" thay vì "tín hiệu 2" — đó là nhờ strsignal. In friendly error messages: instead of "caught signal 2", a program prints "caught signal: Interrupt" via strsignal().8

    Chuẩn hóa muộnStandardized late

    psignal(), strsignal() và sys_siglist không nằm trong SUSv3 nhưng có mặt trên nhiều hệ UNIX; SUSv4 về sau mới bổ sung đặc tả cho psignal() và strsignal().18psignal(), strsignal(), and sys_siglist are not in SUSv3 but are present on many UNIX systems; SUSv4 later added specifications for psignal() and strsignal().18

    Vì sao ưu tiên hàmWhy prefer the function

    Ngoài tính nhạy locale, strsignal() còn kiểm tra biên của sig và trả về NULL nếu không hợp lệ — an toàn hơn so với lập chỉ mục thô vào mảng sys_siglist.18Beyond locale sensitivity, strsignal() also bounds-checks sig and returns NULL if it is invalid — safer than raw indexing into the sys_siglist array.18

Cụm khái niệm 04 Concept cluster 04

Tập tín hiệu, mặt nạ, tín hiệu đang chờ — và một sự thật: tín hiệu chuẩn không xếp hàng Gom tín hiệu, chặn tạm, xem ai đang chờ, và đứng đợi Signal sets, the mask, pending signals — and a truth: standard signals don't queue

Nhiều system call cần biểu diễn một nhóm tín hiệu. Kiểu dữ liệu sigset_t làm việc đó, thao tác qua bộ hàm: sigemptyset() (tập rỗng), sigfillset() (chứa mọi tín hiệu, kể cả realtime), sigaddset()/sigdelset() (thêm/bớt một tín hiệu), và sigismember() (kiểm tra một tín hiệu có trong tập).19 Nhân giữ cho mỗi tiến trình một mặt nạ tín hiệu (signal mask) — tập các tín hiệu đang bị chặn khỏi việc phát; sigprocmask() xem và sửa mặt nạ này, còn sigpending() trả về tập tín hiệu đang chờ.11011

Thay vì xử lý từng tín hiệu một, hệ thống cho ta một "danh sách" tín hiệu (sigset_t): tạo danh sách rỗng, điền đầy, thêm hoặc bớt từng cái, hay hỏi "cái này có trong danh sách không". Dùng danh sách đó, ta bảo máy "hãy cho mấy tín hiệu này đứng chờ" (mặt nạ), rồi có thể hỏi "ai đang chờ ngoài cửa". Nhớ nhé: tập tín hiệu là một danh sách; mặt nạ là danh sách những tín hiệu đang bị cho chờ.

Many system calls must represent a group of signals. The sigset_t type does this, manipulated by a family of functions: sigemptyset() (empty set), sigfillset() (contains every signal, including realtime), sigaddset()/sigdelset() (add/remove one), and sigismember() (test membership).19 The kernel keeps, for each process, a signal mask — the set of signals currently blocked from delivery; sigprocmask() inspects and modifies it, while sigpending() returns the set of pending signals.11011

Định nghĩa. sigprocmask(how, set, oldset) thay đổi mặt nạ theo ba cách tùy how: SIG_BLOCK (thêm set vào mặt nạ), SIG_UNBLOCK (bỏ set khỏi mặt nạ), và SIG_SETMASK (đặt mặt nạ đúng bằng set). Truyền set là NULL chỉ để lấy mặt nạ hiện thời vào oldset mà không đổi gì.110

Nói đơn giản. sigprocmask sửa danh sách "đang cho chờ": thêm vào (SIG_BLOCK), bỏ ra (SIG_UNBLOCK), hay đặt lại cả danh sách (SIG_SETMASK). Nhớ nhé: BLOCK là thêm vào chặn, UNBLOCK là mở ra, SETMASK là thay cả danh sách.

Definition. sigprocmask(how, set, oldset) changes the mask three ways by how: SIG_BLOCK (add set to the mask), SIG_UNBLOCK (remove set), and SIG_SETMASK (set the mask exactly to set). Passing set as NULL just fetches the current mask into oldset without changing anything.110

sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGINT);
sigprocmask(SIG_BLOCK, &s, NULL);
/* ... đoạn tới hạn ... */
sigprocmask(SIG_UNBLOCK, &s, NULL);

sigemptyset(&s); // danh sách rỗng
sigaddset(&s, SIGINT);
sigprocmask(SIG_BLOCK, &s, NULL);
// ...làm việc quan trọng...
sigprocmask(SIG_UNBLOCK, &s, NULL);

sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGINT);
sigprocmask(SIG_BLOCK, &s, NULL);
/* ... critical section ... */
sigprocmask(SIG_UNBLOCK, &s, NULL);

  1. blocking ≠ discarding

    Chặn chỉ hoãn, không vứt — tín hiệu nằm chờ Chặn là bắt chờ, không phải ném đi Blocking defers, it doesn't discard — the signal stays pending

    Khi một tín hiệu bị chặn trong mặt nạ, nó không bị mất: nó trở thành pending và ở đó cho tới khi được bỏ chặn, lúc ấy mới được phát.110 Nỗ lực chặn SIGKILL hay SIGSTOP bị âm thầm bỏ qua — một hệ quả nữa của tính bất khả cưỡng của chúng.1 Nếu ta đổi disposition của một tín hiệu đang chờ, thì khi nó được bỏ chặn, nó sẽ được xử theo disposition mới.1

    Chặn một tín hiệu không làm nó biến mất — nó chỉ đứng chờ ngoài cửa, và sẽ vào ngay khi bạn mở chặn. Riêng SIGKILL và SIGSTOP thì không chặn được (máy lặng lẽ lờ yêu cầu chặn). Nhớ nhé: chặn là cho chờ, mở chặn là tín hiệu ùa vào.

    When a signal is blocked by the mask it is not lost: it becomes pending and stays so until unblocked, at which point it is delivered.110 Attempts to block SIGKILL or SIGSTOP are silently ignored — another consequence of their irresistibility.1 If you change the disposition of a pending signal, then when it is later unblocked it is handled by its new disposition.1

    Bạn gặp nó ở đâuWhere you meet it Khi bảo vệ một đoạn tới hạn khỏi bị cắt ngang: chặn tín hiệu, làm việc, rồi bỏ chặn — tín hiệu dồn lại sẽ được phát sau.10 Khi làm một việc không được phép bị ngắt giữa chừng — chặn tín hiệu trong lúc đó rồi mở lại. When protecting a critical section from interruption: block the signal, do the work, then unblock — the deferred signal is delivered afterward.10
  2. not queued

    Tín hiệu chuẩn không xếp hàng — chờ nhiều lần chỉ còn một Nhiều tín hiệu cùng loại dồn lại chỉ tính một Standard signals don't queue — many pending become one

    Đây là một sự thật dễ gây sốc: mỗi tín hiệu chuẩn chỉ được đánh dấu "đang chờ" một lần duy nhất. Nếu cùng một tín hiệu được sinh ra nhiều lần trong khi nó đang bị chặn, thì khi bỏ chặn nó chỉ được phát một lần — các lần sinh dư "biến mất".1 Tập pending thực chất là một tập hợp (có/không), không phải một hàng đợi đếm số lần. Đây chính là điểm khác cốt lõi với tín hiệu thời gian thực, vốn được xếp hàng.12

    Tưởng tượng chuông cửa chỉ nhớ "có người bấm" chứ không đếm bấm bao nhiêu lần. Tín hiệu chuẩn cũng vậy: nếu nó tới năm lần trong lúc bị chặn, khi mở ra bạn chỉ nhận một lần. Bốn lần kia mất. Nhớ nhé: tín hiệu chuẩn chỉ nhớ "có hay không", không đếm số lần.

    A surprising truth: each standard signal is marked pending only once. If the same signal is generated many times while blocked, then on unblocking it is delivered just once — the extra generations simply "disappear."1 The pending set is really a set (present/absent), not a counting queue. This is the core difference from realtime signals, which are queued.12

    Bạn gặp nó ở đâuWhere you meet it Khi một handler SIGCHLD bỏ sót vài tiến trình con — vì nhiều SIGCHLD dồn lại chỉ phát một lần, nên handler phải lặp để thu hết (Chương 26).1 Khi chương trình "bỏ quên" vài sự kiện — vì nhiều tín hiệu cùng loại dồn lại chỉ tính một lần. When a SIGCHLD handler misses some children — because several SIGCHLDs coalesce into one delivery, so the handler must loop to reap them all (Chapter 26).1

    Vì sao chỉ "có/không"Why only present/absent

    Trạng thái "đang chờ" của tập tín hiệu chuẩn được biểu diễn bằng một mặt nạ bit — mỗi tín hiệu một bit. Một bit chỉ ghi được "có" hoặc "không", nên tự nó không thể đếm số lần tín hiệu được sinh. Đó là lý do kỹ thuật khiến tín hiệu chuẩn không xếp hàng.1The pending state of standard signals is represented by a bit mask — one bit per signal. A single bit records only "present" or "absent," so it inherently cannot count generations. That is the technical reason standard signals do not queue.1

    Khác biệt với realtimeDifference from realtime

    Tín hiệu thời gian thực giữ một hàng đợi riêng nên mỗi lần sinh đều được phát đúng một lần và còn kèm dữ liệu đi theo. Khi cần đếm chính xác số sự kiện, đó là lựa chọn phù hợp hơn tín hiệu chuẩn.12Realtime signals maintain a queue, so every generation is delivered exactly once and can carry accompanying data. When you need an exact count of events, they are a better fit than standard signals.12

  3. pause()

    pause(): đứng yên cho tới khi có tín hiệu pause(): ngủ cho tới khi có tín hiệu đánh thức pause(): sleep until a signal arrives

    pause() treo tiến trình gọi cho tới khi một tín hiệu được bắt (hoặc một tín hiệu kết thúc tiến trình).112 Đây là cách đơn giản nhất để một tiến trình "ngủ" mà không tốn CPU, rồi tỉnh dậy đúng khi có việc — thường dùng trong các ví dụ chờ tín hiệu của chương này.1 Khi tín hiệu được xử lý, pause() trở về với lỗi EINTR.112

    pause() là cách nói "tôi ngủ đây, có tín hiệu thì gọi tôi dậy". Trong lúc ngủ, chương trình không tốn sức máy. Khi một tín hiệu tới và được xử lý xong, nó tỉnh dậy. Nhớ nhé: pause() là nút "ngủ cho tới khi có tín hiệu".

    pause() suspends the calling process until a signal is caught (or one terminates it).112 It is the simplest way for a process to "sleep" without burning CPU and wake exactly when there is work — used in this chapter's signal-waiting examples.1 After the signal is handled, pause() returns with the error EINTR.112

    Bạn gặp nó ở đâuWhere you meet it Trong các chương trình nhỏ chỉ chờ một tín hiệu để hành động — một vòng chờ tối giản, không bận rộn quay CPU.12 Khi một chương trình đứng im chờ Ctrl-C hay một tín hiệu khác rồi mới làm gì đó. In small programs that just wait for one signal to act — a minimal wait loop that does not spin the CPU.12

    Một cuộc đua rình rậpA lurking race

    pause() có một cuộc đua kinh điển: nếu tín hiệu đến giữa lúc kiểm tra điều kiện và lúc gọi pause(), chương trình có thể ngủ mãi. Cách giải đúng là sigsuspend(), kết hợp mở mặt nạ và chờ một cách nguyên tử — sách phát triển ý này ở các chương sau.1pause() has a classic race: if the signal arrives between testing a condition and calling pause(), the program may sleep forever. The correct fix is sigsuspend(), which atomically unblocks and waits — the book develops this in later chapters.1

    Trả về EINTRIt returns EINTR

    pause() luôn trả về −1 với errno đặt là EINTR sau khi một handler chạy xong — một ví dụ điển hình của việc một system call bị tín hiệu cắt ngang, chủ đề được bàn kỹ ở Chương 21.112pause() always returns −1 with errno set to EINTR after a handler runs — a textbook case of a system call interrupted by a signal, a topic treated fully in Chapter 21.112

Ba ngộ nhận thường gặp Three common misconceptions

“kill() nghĩa là giết một tiến trình” “kill() means killing a process”

Ngộ nhận: kill() luôn kết thúc tiến trình đích.

Thực tế: kill() chỉ gửi một tín hiệu; cái tên là di sản lịch sử. Có thể gửi SIGCONT (cho chạy tiếp), hay tín hiệu null 0 (không gửi gì, chỉ kiểm tra tồn tại).15

Myth: kill() always terminates the target.

Reality: kill() merely sends a signal; the name is historical. You can send SIGCONT (resume), or the null signal 0 (sends nothing, just checks existence).15

“Chặn và bỏ qua là như nhau” “Blocking and ignoring are the same”

Ngộ nhận: chặn một tín hiệu cũng vứt bỏ nó như SIG_IGN.

Thực tế: SIG_IGN vứt bỏ tín hiệu khi phát; chặn chỉ hoãn — tín hiệu thành pending và sẽ được phát khi bỏ chặn.110

Myth: blocking a signal discards it, just like SIG_IGN.

Reality: SIG_IGN discards the signal on delivery; blocking only defers it — the signal becomes pending and is delivered once unblocked.110

“Mỗi lần tín hiệu tới đều được nhận riêng” “Every signal arrival is received separately”

Ngộ nhận: gửi cùng một tín hiệu năm lần thì handler chạy đủ năm lần.

Thực tế: tín hiệu chuẩn không xếp hàng — nhiều lần sinh khi đang chờ chỉ gộp thành một lần phát. Cần đếm chính xác thì dùng tín hiệu thời gian thực.12

Myth: sending the same signal five times runs the handler five times.

Reality: standard signals do not queue — multiple generations while pending coalesce into one delivery. For an exact count, use realtime signals.12

Tự kiểm Self-check

Năm câu hỏi để biết bạn đã nắm nền tảng về tín hiệu Năm câu hỏi kiểm tra nhanh Five questions to check you grasped the signal fundamentals

Trả lời được những câu này nghĩa là bạn đã nắm vòng đời, cách đổi disposition, cách gửi và cách điều phối tín hiệu — nền cho Chương 21 về thiết kế trình xử lý tín hiệu.

Nếu trả lời được, nghĩa là bạn đã hiểu chương này.

Answering these means you grasp the lifecycle, changing disposition, sending, and controlling signals — the groundwork for Chapter 21 on designing signal handlers.

  1. Mô tả vòng đời của một tín hiệu: sinh ra, đang chờ, được phát. Năm hành động mặc định là gì?

    Gợi ý sâu hơn: vì sao mã nên tham chiếu tín hiệu bằng tên biểu tượng chứ không bằng số?

  2. Vì sao sigaction() được ưu tiên hơn signal()? Ba hằng SIG_DFL, SIG_IGN, SIG_ERR có nghĩa gì?

    Gợi ý sâu hơn: vì sao không thể dùng signal() để chỉ đọc disposition mà không đổi nó?

  3. SIGKILL và SIGSTOP khác mọi tín hiệu khác ở điểm nào, và vì sao phải như vậy?

    Gợi ý sâu hơn: điều gì sẽ xảy ra nếu một tiến trình có thể bắt hoặc chặn SIGKILL?

  4. Tín hiệu null (0) dùng để làm gì, và ESRCH với EPERM cho ta biết điều gì khác nhau?

    Gợi ý sâu hơn: vì sao kiểm tra tồn tại bằng tín hiệu null có thể sai do PID được tái sử dụng?

  5. Phân biệt "chặn" với "bỏ qua" một tín hiệu. Vì sao gửi cùng một tín hiệu chuẩn năm lần có thể chỉ được nhận một lần?

    Gợi ý sâu hơn: ba giá trị SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK của sigprocmask() làm gì?

  1. Một tín hiệu đi qua những bước nào từ lúc sinh ra tới lúc được xử lý?

  2. Có mấy cách dặn máy phản ứng với một tín hiệu, và nên dùng cách nào?

  3. Vì sao kill -9 luôn tắt được một chương trình treo?

  4. Làm sao kiểm tra một tiến trình còn sống mà không làm phiền nó?

  5. "Chặn" một tín hiệu khác gì với "lờ đi" nó?

  1. Describe a signal's lifecycle: generated, pending, delivered. What are the five default actions?

  2. Why is sigaction() preferred over signal()? What do SIG_DFL, SIG_IGN, and SIG_ERR mean?

  3. How do SIGKILL and SIGSTOP differ from all other signals, and why must they?

  4. What is the null signal (0) used for, and what do ESRCH versus EPERM tell you?

  5. Distinguish "blocking" from "ignoring" a signal. Why can sending the same standard signal five times be received only once?

Khép lại chương Closing the chapter

Một tín hiệu là tiếng gõ cửa bất đồng bộ do nhân điều phối: nó đi qua vòng đời sinh–chờ–phát, mỗi loại có hành động mặc định riêng, ta đổi cách đáp lại nó qua sigaction(), gửi nó đi đúng quyền với kill(), và điều phối thời điểm bằng mặt nạ tín hiệu — luôn nhớ rằng tín hiệu chuẩn không xếp hàng. Nắm được nền này, bạn đã sẵn sàng cho câu hỏi kế tiếp — viết một trình xử lý tín hiệu cho an toàn và đúng đắn.1

Tín hiệu là tiếng gõ cửa bất ngờ: có vòng đời rõ ràng, có nếp xử mặc định, có cách đổi cách đáp, cách gửi, và cách bảo nó chờ. Hiểu bộ khung này là đủ để đi tiếp sang chuyện viết trình xử lý tín hiệu. Nhớ nhé: tín hiệu là tiếng gõ cửa — bạn chọn cách mở, cách gửi, và cách bảo nó đợi.

A signal is an asynchronous knock at the door orchestrated by the kernel: it runs a generate–pending–deliver lifecycle, each type has its own default action, you change your response via sigaction(), send it with the right permission via kill(), and control its timing with the signal mask — always remembering that standard signals do not queue. Grasp this groundwork and you are ready for the next question — how to write a signal handler safely and correctly.1