Signal Linux biasanya memotong alur thread melalui delivery asinkron. signalfd() menyediakan boundary berbeda untuk signal terpilih: signal tetap diblokir pada thread yang relevan, lalu instance yang pending dikonsumsi dengan membaca file descriptor.
Mekanisme signal tidak berubah menjadi byte stream. Kernel tetap memelihara state signal serta aturan delivery process-directed dan thread-directed. signalfd menambahkan interface file descriptor untuk menerima signal dari set yang dikonfigurasi.
Blocking dan mask descriptor adalah state yang terpisah
Sebuah signalfd memiliki signal-set mask yang menentukan signal mana yang dapat diterimanya. Mask tersebut tidak memblokir signal pada thread. Pemakaian normal karena itu memasangkan pembuatan descriptor dengan operasi signal mask.
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);Kedua mask memiliki fungsi berbeda. Signal mask milik thread mencegah delivery asinkron biasa. Mask milik signalfd memilih pending signal yang dapat dikembalikan oleh read().
Perbedaan ini penting pada program multithread karena setiap thread memiliki signal mask sendiri. Memblokir signal pada satu thread tidak otomatis memblokirnya pada thread lain. Process yang memusatkan process-directed signal melalui satu signalfd umumnya mengatur mask sebelum worker thread dibuat agar blocked set tersebut diwariskan.
read mengonsumsi pending signal
Saat signal terpilih berstatus pending, read() mengembalikan satu atau beberapa record struct signalfd_siginfo sebanyak yang muat di buffer. Signal yang sudah dikonsumsi tidak lagi pending dan tidak dapat diterima kembali melalui interface penantian signal lain.
struct signalfd_siginfo si;
ssize_t n = read(sfd, &si, sizeof(si));
if (n == sizeof(si)) {
if (si.ssi_signo == SIGTERM) {
/* begin shutdown */
}
}Record tersebut membawa field seperti nomor signal dan, jika berlaku, informasi pengirim atau queued signal. Layout-nya khusus untuk API signalfd, bukan objek siginfo_t mentah.
Dengan SFD_NONBLOCK, read() saat tidak ada signal terpilih yang pending gagal dengan EAGAIN. Tanpa mode nonblocking, operasi tersebut dapat menunggu signal yang memenuhi syarat.
Readiness membuat signal dapat berbagi event loop
signalfd menjadi readable ketika sedikitnya satu signal dari set yang dikonfigurasi berstatus pending dan tersedia bagi caller. Descriptor itu dapat ikut dipantau melalui poll(), select(), dan epoll bersama socket, timer, pipe, serta sumber event berbasis descriptor lainnya.
epoll
|
+-- listening socket
+-- timerfd
+-- signalfd
|
+-- SIGTERM
`-- SIGHUPBoundary control flow berubah. Alih-alih memindahkan eksekusi ke signal handler pada instruction boundary yang tidak ditentukan aplikasi, program dapat memproses notifikasi signal terpilih pada titik normal di event loop.
Hal itu tidak membuat semantics signal identik dengan socket. Standard signal tetap mengikuti aturan pending yang biasa, dan descriptor menyediakan penerimaan signal, bukan queue bebas yang menyimpan setiap generation event.
Process-directed dan thread-directed signal tetap mempertahankan scope
Pembacaan signalfd mengikuti aturan targeting signal Linux. Sebuah thread dapat membaca signal yang diarahkan kepadanya sendiri serta process-directed signal yang tersedia bagi thread group. Descriptor tersebut tidak dapat dipakai untuk mengonsumsi signal yang diarahkan khusus ke thread lain.
Boundary ini mudah terlewat jika satu signalfd dianggap sebagai mailbox global untuk seluruh process. File descriptor memang dapat diakses process seperti descriptor lain, tetapi kelayakan sebuah signal tetap mengikuti scope signal.
Mask per thread juga tetap menentukan hasil. Jika thread lain membiarkan process-directed signal terpilih dalam keadaan unblocked, kernel dapat melakukan delivery normal ke thread tersebut sehingga signal tidak tertinggal sebagai pending item untuk consumer signalfd.
Beberapa descriptor bersaing untuk signal yang overlap
Sebuah process dapat membuat beberapa objek signalfd dengan mask berbeda. Jika signal yang sama muncul pada lebih dari satu mask descriptor, satu occurrence yang pending dapat dikonsumsi melalui salah satu descriptor yang memenuhi syarat; pembacaan tidak membuat salinan untuk setiap descriptor.
signalfd A: SIGTERM, SIGHUP
signalfd B: SIGTERM
pending SIGTERM
|
+--> readable melalui descriptor yang memenuhi syarat
konsumsi menghapus occurrence yang pendingMask yang overlap berarti ada beberapa jalur penerimaan, bukan multicast delivery.
Mask descriptor juga dapat diganti dengan memanggil signalfd() menggunakan descriptor lama sebagai argumen pertama. Perubahan mask descriptor dan perubahan signal mask milik thread tetap merupakan dua operasi terpisah.
SIGKILL dan SIGSTOP tetap berada di luar interface
SIGKILL dan SIGSTOP tidak dapat diblokir. Jika salah satunya dimasukkan ke mask untuk signalfd(), Linux mengabaikannya secara diam-diam pada interface ini.
Pembatasan tersebut mengikuti aturan signal yang lebih kuat: kedua signal itu tidak dapat dicegat atau ditunda oleh mekanisme signal userspace biasa. Jalur penerimaan berbasis file descriptor tidak dapat melewati properti tersebut.
Fault sinkron bukan kasus pengganti signal handler
Signal yang muncul secara sinkron akibat execution fault, seperti SIGSEGV karena akses memory tidak valid atau SIGFPE akibat arithmetic fault, bukan jalur yang sesuai untuk signalfd. Dokumentasi Linux menempatkan signal jenis ini pada signal handler jika interception diperlukan untuk recovery atau diagnosis.
Boundary arsitekturalnya jelas. signalfd sesuai untuk control signal yang berasal dari luar atau diterima secara asinkron, seperti termination dan reload notification. Mekanisme ini tidak mengubah fault handling menjadi I/O event-loop biasa.
fork dan epoll memiliki edge case khusus
Child hasil fork() mewarisi file descriptor dan state signal mask. Child dapat membaca signal yang dikirim kepadanya melalui signalfd yang diwarisi. Namun ada interaksi epoll yang lebih sempit: signalfd warisan yang sudah didaftarkan ke sebuah instance epoll sebelum fork tidak membuat epoll_wait() milik child melaporkan readiness untuk signal yang dikirim ke child.
Child yang memerlukan susunan tersebut dapat membuat signalfd sendiri setelah fork dan mendaftarkan descriptor itu ke event loop miliknya.
Boundary ini berbeda dari aturan inheritance descriptor secara umum. Descriptor warisan masih dapat dibaca; bagian yang memiliki perilaku readiness khusus adalah registrasi epoll yang sudah ada sebelum fork.
State descriptor dapat diperiksa melalui procfs
Linux mengekspos signal mask milik signalfd melalui entry /proc/<pid>/fdinfo/<fd> yang terkait. Field sigmask menampilkan mask descriptor dalam bentuk hexadecimal.
State tersebut berguna ketika sebuah process tampak sudah memblokir control signal tetapi event loop tidak pernah melihatnya. Diagnosis kemudian memiliki sedikitnya tiga pertanyaan berbeda: thread mana yang memblokir signal, signal mana yang diterima descriptor, dan apakah event loop memantau descriptor yang tepat.
signalfd dengan demikian bukan pengganti semantics signal Linux. Ia menjadi jembatan antara semantics tersebut dan control flow berbasis descriptor: signal terpilih tetap tunduk pada signal mask serta targeting rule, sedangkan proses penerimaannya berubah menjadi readable event yang dapat diproses pada scheduling point yang sama dengan pekerjaan event-loop lain.