Linux signalfd Memindahkan Delivery Signal ke Readiness File Descriptor

signalfd() mengubah antarmuka konsumsi untuk signal Linux tertentu. Alih-alih mengatur agar asynchronous signal handler berjalan saat signal tersebut dikirim, sebuah proses dapat memblokir signal lalu menerima informasinya dengan membaca file descriptor. Descriptor itu dapat dipantau melalui poll(), epoll, dan mekanisme readiness terkait, sehingga penanganan signal dapat memakai batas dispatch yang sama dengan socket, pipe, timer, dan objek pollable lain.

Mekanisme ini tidak menggantikan semantik signal Linux. Pembentukan signal, delivery yang diarahkan ke proses atau thread, mask, pending state, serta aturan antrean standard signal dan real-time signal tetap berlaku. signalfd mengubah antarmuka untuk mengonsumsi signal yang berstatus pending dan termasuk dalam mask miliknya.

Signal mask menjadi bagian dari kontrak delivery

Sebuah signalfd dibuat dengan signal set. Operasi baca yang berhasil mengembalikan satu atau beberapa record signalfd_siginfo untuk signal pending yang termasuk dalam set tersebut. Agar model ini mencegah asynchronous delivery biasa, signal yang sama juga perlu diblokir pada thread yang berpotensi menerimanya.

Perbedaan itu penting karena mask yang diberikan kepada signalfd() dan blocked-signal mask milik thread memiliki peran terpisah. Mask pada descriptor memilih signal pending yang dapat dikonsumsi melalui descriptor. Mask pada thread menentukan signal yang diblokir dari delivery biasa ke thread tersebut.

Pola pada tingkat proses biasanya memblokir set yang dipilih sebelum membuat thread tambahan. Thread baru mewarisi signal mask milik pembuatnya, sehingga status blocked ikut terbawa melalui pembuatan thread normal. Sebuah thread event loop khusus kemudian dapat memantau descriptor tersebut.

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);

if (pthread_sigmask(SIG_BLOCK, &mask, NULL) != 0)
    fail();

int sfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
if (sfd == -1)
    fail();

Pola ini merupakan constraint desain proses, bukan properti yang otomatis dipaksakan oleh signalfd(). Jika thread lain membiarkan signal terpilih dalam keadaan unblocked, Linux dapat mengirim process-directed signal yang memenuhi syarat ke thread tersebut melalui mekanisme signal biasa, alih-alih membiarkannya pending untuk descriptor.

Operasi baca mengonsumsi signal pending

Readiness pada signalfd berarti setidaknya satu signal yang cocok dengan mask descriptor sedang pending. read() memindahkan record signalfd_siginfo terstruktur dan mengonsumsi signal pending terkait. Buffer harus cukup besar untuk setidaknya satu record lengkap; buffer yang lebih besar dapat menerima beberapa record dalam satu operasi.

Dengan SFD_NONBLOCK, operasi baca tanpa signal pending yang cocok gagal dengan EAGAIN. Perilaku ini membuat descriptor sesuai dengan pola event loop nonblocking. Pada level-triggered readiness, signal yang belum dibaca membuat descriptor tetap readable. Pada epoll edge-triggered, disiplin drain yang sama dengan descriptor nonblocking lain berlaku: pemrosesan dilanjutkan sampai tidak ada record yang dapat dibaca tanpa blocking.

Record membawa field yang berasal dari informasi signal, termasuk nomor signal dan, bila relevan, PID pengirim, UID pengirim, queued integer data, data timer, atau status child. Ketersediaan dan maknanya bergantung pada sumber signal. Aplikasi tetap perlu menafsirkan data berdasarkan jenis signal dan tidak dapat menganggap setiap field selalu terisi.

Standard signal dan real-time signal mempertahankan semantik antreannya

signalfd tidak mengubah setiap kemunculan signal menjadi entri antrean yang persisten. Standard signal Linux tidak mengantre banyak instance dengan cara yang sama seperti real-time signal. Jika sebuah standard signal sudah pending, instance tambahan dari signal yang sama dapat melebur ke pending state yang telah ada. Karena itu, pembacaan dari signalfd tidak dapat membentuk kembali hitungan kejadian yang memang tidak dipertahankan oleh model signal dasarnya.

Real-time signal memiliki semantik antrean dan mempertahankan beberapa instance pending, dengan batas resource sistem. Aturan urutannya juga tetap merupakan aturan signal, bukan properti baru yang diperkenalkan file descriptor.

Batas ini penting bagi desain event loop. Sebuah record SIGTERM dapat merepresentasikan status termination signal yang pending, tetapi bukan counter andal untuk setiap upaya pengiriman SIGTERM. Jika aplikasi memerlukan penghitungan tanpa kehilangan atau delivery payload, primitive komunikasi yang dipakai harus menyediakan semantik tersebut secara eksplisit.

Process-directed dan thread-directed signal tetap mengikuti aturan target

Linux membedakan signal yang diarahkan ke proses dari signal yang diarahkan ke thread tertentu. Process-directed signal dapat dikirim ke thread yang memenuhi syarat sesuai aturan delivery signal kernel. Thread-directed signal menargetkan thread yang ditentukan.

signalfd bekerja di dalam model yang sudah ada itu. Sebuah thread dapat membaca signal yang pending untuk proses serta signal yang pending untuk thread pembaca jika cocok dengan mask descriptor. Antarmuka ini bukan mekanisme umum bagi satu thread untuk mengonsumsi signal yang pending khusus untuk thread lain.

Constraint tersebut dapat terlihat ketika library membuat thread atau mengubah signal mask secara internal. Konsumsi signal terpusat bergantung pada kebijakan masking yang konsisten di seluruh proses. Kode yang diam-diam melakukan unblock terhadap signal yang dikelola secara terpusat dapat membuka kembali asynchronous delivery pada thread tersebut.

SIGKILL dan SIGSTOP tetap tidak dapat diintersep

SIGKILL dan SIGSTOP tidak dapat di-catch, diblokir, atau diabaikan. Menyertakan salah satu signal itu dalam mask yang diberikan kepada signalfd() tidak membuatnya dapat dikonsumsi melalui descriptor. Linux secara diam-diam mengecualikannya dari set efektif yang dipakai antarmuka ini.

Ini adalah batas pada tingkat kernel, bukan keterbatasan event loop. Desain yang mengarahkan shutdown signal biasa melalui signalfd tetap tidak dapat mengubah forced termination atau forced stop menjadi event yang dikelola aplikasi.

Masa hidup descriptor dan pembaruan mask adalah state terpisah

Pemanggilan signalfd() dengan fd bernilai -1 membuat descriptor baru. Memberikan descriptor signalfd yang sudah ada akan memperbarui signal set yang terkait dengan descriptor tersebut. Pembaruan mask descriptor tidak memperbarui blocked-signal mask milik thread mana pun. Aplikasi yang mengubah set secara dinamis harus menjaga kedua state tetap konsisten dengan kebijakan delivery yang dimaksud.

SFD_CLOEXEC mengontrol pewarisan descriptor melewati execve() dengan cara yang sama seperti close-on-exec state pada descriptor lain. SFD_NONBLOCK mengontrol perilaku blocking untuk operasi baca. Kedua flag itu tidak mengubah signal mana yang diblokir pada thread.

Setelah fork(), child mewarisi descriptor, tetapi pemantauan readiness memiliki detail tambahan pada batas proses. Secara khusus, instance epoll yang diwarisi melewati fork() tidak melaporkan signal yang dibentuk oleh child melalui registrasi signalfd milik parent dengan cara yang sama seperti registrasi yang dibuat untuk event loop milik child sendiri. Proses yang berjalan mandiri setelah fork sebaiknya menetapkan kepemilikan event secara eksplisit dan tidak memperlakukan polling state warisan sebagai router signal lintas proses yang transparan.

File descriptor mempersempit permukaan eksekusi asynchronous

Signal handler tradisional berjalan secara asynchronous terhadap alur program biasa dan terbatas pada operasi yang aman dalam konteks tersebut. Memindahkan konsumsi signal terpilih ke signalfd membuat aplikasi dapat memproses signal itu selama eksekusi event loop biasa. Untuk signal yang diblokir tersebut, jalur handler tidak lagi perlu mengubah shared state dari konteks signal asynchronous.

Manfaat arsitektural itu memiliki batas yang jelas: manfaat tersebut berlaku hanya selama kebijakan masking mempertahankan signal pada jalur pending yang dikonsumsi signalfd. Signal lain yang tidak diblokir masih dapat memanggil handler atau menjalankan default action. Proses karena itu perlu menjaga dua dimensi secara terpisah: signal disposition dan signal masking.

Antarmuka ini sesuai untuk aplikasi yang sudah memakai readiness sebagai synchronization boundary. signalfd membawa state signal terpilih ke boundary tersebut sebagai structured read, sementara aturan targeting signal Linux, semantik pending state, dan signal yang tidak dapat diintersep tetap berlaku di bawahnya.