Bagaimana cara menghindari penggunaan printf di penangan sinyal?

87

Karena printfbukan reentrant, maka seharusnya tidak aman digunakan dalam penangan sinyal. Tetapi saya telah melihat banyak kode contoh yang menggunakan printfcara ini.

Jadi pertanyaan saya adalah: kapan kita perlu menghindari penggunaan printfdalam penangan sinyal, dan apakah ada pengganti yang disarankan?

Yu Hao
sumber
12
Jawaban sederhana dan tidak terlalu berguna untuk pertanyaan dalam judul Anda: Lihat printfpanggilan itu di penangan sinyal itu? Hapus.
Keith Thompson
6
Halo Yu Hao! Saya rasa Anda akan menemukan link yang sangat menarik untuk dibaca. "Gunakan fungsi reentrant untuk penanganan sinyal yang lebih aman" Saya telah membacanya setelah sekian lama, saya ingin berbagi buatan di sini dengan Anda. Semoga anda menikmati.
Grijesh Chauhan

Jawaban:

58

Anda bisa menggunakan beberapa variabel flag, menyetel flag itu di dalam penangan sinyal, dan berdasarkan printf()fungsi panggilan flag tersebut di main () atau bagian lain dari program selama operasi normal.

Tidak aman untuk memanggil semua fungsi, seperti printf, dari dalam penangan sinyal. Teknik yang berguna adalah menggunakan penangan sinyal untuk mengatur flagdan kemudian memeriksanya flag dari program utama dan mencetak pesan jika diperlukan.

Perhatikan dalam contoh di bawah ini, penangan sinyal ding () menetapkan sebuah flag alarm_firedke 1 saat SIGALRM ditangkap dan dalam alarm_firednilai fungsi utama diperiksa untuk memanggil printf dengan benar secara kondisional.

Referensi: Memulai Pemrograman Linux, Edisi ke-4 , Dalam buku ini persis kode Anda dijelaskan (apa yang Anda inginkan), Bab 11: Proses dan Sinyal, halaman 484

Selain itu, Anda perlu berhati-hati dalam menulis fungsi penangan karena mereka dapat dipanggil secara asinkron. Artinya, seorang penangan dapat dipanggil kapan saja dalam program, secara tidak terduga. Jika dua sinyal tiba dalam interval yang sangat singkat, satu pawang dapat berjalan di dalam pawang lainnya. Dan dianggap praktik yang lebih baik untuk menyatakan volatile sigatomic_t, jenis ini selalu diakses secara atomik, hindari ketidakpastian tentang mengganggu akses ke variabel. (baca: Akses Data Atom dan Penanganan Sinyal untuk perincian detail).

Baca Menentukan Penangan Sinyal : untuk mempelajari cara menulis fungsi penangan sinyal yang dapat ditetapkan dengan fungsi signal()atau sigaction().
Daftar fungsi resmi di halaman manual , memanggil fungsi ini di dalam penangan sinyal aman.

Grijesh Chauhan
sumber
18
Ini dianggap praktik yang lebih baik untuk menyatakanvolatile sigatomic_t alarm_fired;
Basile Starynkevitch
1
@GrijeshChauhan: jika kita bekerja dalam kode produk, maka kita tidak dapat memanggil fungsi jeda, aliran bisa di mana saja ketika sinyal terjadi, jadi dalam hal ini kita benar-benar tidak tahu di mana harus menyimpannya "if (alarm_fired) printf (" Ding! \ n ");" dalam kode.
pankaj kushwaha
@pankajkushwaha ya, Anda benar, itu menderita kondisi ras
Grijesh Chauhan
@GrijeshChauhan, Ada dua hal yang saya tidak bisa mengerti. 1. Bagaimana Anda tahu kapan harus memeriksa bendera? Jadi, akan ada beberapa titik pemeriksaan dalam kode di hampir setiap titik untuk dicetak. 2. Pasti akan ada kondisi balapan dimana dalam sinyal bisa dipanggil sebelum registrasi sinyal atau sinyal bisa muncul setelah check point. Saya pikir ini hanya akan membantu mencetak dalam beberapa kondisi tetapi tidak sepenuhnya menyelesaikan masalah.
Darshan b
55

Masalah utama adalah bahwa jika sinyal menyela malloc()atau fungsi serupa, keadaan internal mungkin sementara tidak konsisten saat memindahkan blok memori antara daftar yang bebas dan digunakan, atau operasi serupa lainnya. Jika kode dalam penangan sinyal memanggil fungsi yang kemudian dipanggil malloc(), ini dapat merusak sepenuhnya manajemen memori.

Standar C mengambil pandangan yang sangat konservatif tentang apa yang dapat Anda lakukan di penangan sinyal:

ISO / IEC 9899: 2011 §7.14.1.1 signalFungsi

¶5 Jika sinyal terjadi selain sebagai hasil pemanggilan fungsi abortatau raise, perilaku tidak ditentukan jika penangan sinyal merujuk ke objek apa pun dengan durasi penyimpanan statis atau utas yang bukan objek atom bebas kunci selain dengan menetapkan nilai ke objek yang dideklarasikan sebagai volatile sig_atomic_t, atau penangan sinyal memanggil fungsi apa pun di pustaka standar selain abortfungsi, _Exitfungsi, quick_exitfungsi, atau signalfungsi dengan argumen pertama sama dengan nomor sinyal yang sesuai dengan sinyal yang menyebabkan pemanggilan penangan. Selanjutnya, jika pemanggilan ke signalfungsi tersebut menghasilkan SIG_ERRpengembalian, nilai dari errnotidak dapat ditentukan. 252)

252) Jika ada sinyal yang dihasilkan oleh penangan sinyal asinkron, perilakunya tidak ditentukan.

POSIX jauh lebih murah hati tentang apa yang dapat Anda lakukan di penangan sinyal.

Konsep Signal dalam edisi POSIX 2008 mengatakan:

Jika prosesnya multi-threaded, atau jika proses single-threaded dan penangan sinyal dijalankan selain sebagai hasil dari:

  • Proses pemanggilan abort(), raise(), kill(), pthread_kill(), atau sigqueue()untuk menghasilkan sinyal yang tidak diblokir

  • Sebuah sinyal tertunda sedang dibuka dan dikirim sebelum panggilan yang membuka blokir itu kembali

perilaku tidak ditentukan jika penangan sinyal merujuk ke objek selain errnodengan durasi penyimpanan statis selain dengan menetapkan nilai ke objek yang dideklarasikan sebagai volatile sig_atomic_t, atau jika penangan sinyal memanggil fungsi apa pun yang ditentukan dalam standar ini selain dari salah satu fungsi yang tercantum di tabel berikut.

Tabel berikut menjelaskan serangkaian fungsi yang harus aman untuk sinyal asinkron. Oleh karena itu, aplikasi dapat memanggilnya, tanpa batasan, dari fungsi penangkap sinyal:

Semua fungsi yang tidak ada dalam tabel di atas dianggap tidak aman sehubungan dengan sinyal. Di hadapan sinyal, semua fungsi yang ditentukan oleh volume POSIX.1-2008 ini akan berperilaku seperti yang didefinisikan ketika dipanggil dari atau diinterupsi oleh fungsi penangkap sinyal, dengan satu pengecualian: ketika sinyal mengganggu fungsi yang tidak aman dan sinyal- fungsi penangkapan memanggil fungsi yang tidak aman, perilakunya tidak ditentukan.

Operasi yang mendapatkan nilai errnodan operasi yang menetapkan nilai errnoharus aman dengan sinyal asinkron.

Ketika sinyal dikirim ke utas, jika aksi sinyal itu menentukan terminasi, berhenti, atau melanjutkan, seluruh proses harus dihentikan, dihentikan, atau dilanjutkan, masing-masing.

Namun, printf()keluarga fungsi tersebut terutama tidak ada dalam daftar itu dan mungkin tidak dapat dipanggil dengan aman dari penangan sinyal.

The POSIX 2016 pembaruan memperluas daftar fungsi yang aman untuk memasukkan, khususnya, sejumlah besar fungsi dari <string.h>, yang merupakan tambahan yang sangat berharga (atau adalah pengawasan sangat frustasi). Daftarnya sekarang:

Akibatnya, Anda akhirnya menggunakan write()tanpa dukungan pemformatan yang disediakan oleh printf()dkk, atau Anda akhirnya menyetel bendera yang Anda uji (secara berkala) di tempat yang sesuai dalam kode Anda. Teknik ini dengan cakap ditunjukkan oleh jawaban Grijesh Chauhan .


Fungsi C standar dan keamanan sinyal

chqrlie mengajukan pertanyaan menarik, yang saya tidak punya lebih dari jawaban parsial:

Mengapa sebagian besar fungsi string dari <string.h>atau fungsi kelas karakter dari <ctype.h>dan banyak lagi fungsi library standar C tidak ada dalam daftar di atas? Implementasi harus sengaja dibuat jahat untuk membuat strlen()panggilan tidak aman dari penangan sinyal.

Untuk banyak fungsi di <string.h>, sulit untuk melihat mengapa mereka tidak dinyatakan aman async-sinyal, dan saya setuju dengan strlen()adalah contoh utama, bersama dengan strchr(), strstr(), dll Di sisi lain, fungsi lain seperti strtok(), strcoll()dan strxfrm()agak rumit dan cenderung tidak aman untuk sinyal asinkron. Karena strtok()mempertahankan status antar panggilan, dan penangan sinyal tidak dapat dengan mudah membedakan apakah beberapa bagian dari kode yang digunakan strtok()akan kacau. Fungsi strcoll()dan strxfrm()bekerja dengan data sensitif lokal, dan memuat lokal melibatkan semua jenis pengaturan status.

Fungsi (makro) dari <ctype.h>semuanya peka terhadap lokal, dan oleh karena itu dapat mengalami masalah yang sama seperti strcoll()dan strxfrm().

Saya merasa sulit untuk melihat mengapa fungsi matematika dari <math.h>tidak async-signal safe, kecuali itu karena mereka dapat dipengaruhi oleh SIGFPE (pengecualian floating point), meskipun tentang satu-satunya saat saya melihat salah satu dari hari-hari ini adalah untuk integer pembagian dengan nol. Ketidakpastian serupa muncul dari <complex.h>, <fenv.h>dan <tgmath.h>.

Beberapa fungsi di <stdlib.h>dapat dikecualikan - abs()misalnya. Yang lainnya secara khusus bermasalah: malloc()dan keluarga adalah contoh utama.

Penilaian serupa dapat dibuat untuk header lain dalam Standar C (2011) yang digunakan dalam lingkungan POSIX. (Standar C sangat terbatas sehingga tidak ada minat untuk menganalisisnya dalam lingkungan Standar C murni.) Yang ditandai 'tergantung-lokal' tidak aman karena memanipulasi lokal mungkin memerlukan alokasi memori, dll.

  • <assert.h>- Mungkin tidak aman
  • <complex.h>- Mungkin aman
  • <ctype.h> - Tidak aman
  • <errno.h> - Aman
  • <fenv.h>- Mungkin tidak aman
  • <float.h> - Tidak ada fungsi
  • <inttypes.h> - Fungsi sensitif lokal (tidak aman)
  • <iso646.h> - Tidak ada fungsi
  • <limits.h> - Tidak ada fungsi
  • <locale.h> - Fungsi sensitif lokal (tidak aman)
  • <math.h>- Mungkin aman
  • <setjmp.h> - Tidak aman
  • <signal.h> - Diizinkan
  • <stdalign.h> - Tidak ada fungsi
  • <stdarg.h> - Tidak ada fungsi
  • <stdatomic.h>- Mungkin aman, mungkin tidak aman
  • <stdbool.h> - Tidak ada fungsi
  • <stddef.h> - Tidak ada fungsi
  • <stdint.h> - Tidak ada fungsi
  • <stdio.h> - Tidak aman
  • <stdlib.h> - Tidak semua aman (beberapa diperbolehkan; yang lainnya tidak)
  • <stdnoreturn.h> - Tidak ada fungsi
  • <string.h> - Tidak semuanya aman
  • <tgmath.h>- Mungkin aman
  • <threads.h>- Mungkin tidak aman
  • <time.h>- Bergantung pada lokal (tetapi time()diizinkan secara eksplisit)
  • <uchar.h> - Bergantung pada lokal
  • <wchar.h> - Bergantung pada lokal
  • <wctype.h> - Bergantung pada lokal

Menganalisis header POSIX akan… lebih sulit karena ada banyak dari mereka, dan beberapa fungsi mungkin aman tetapi banyak yang tidak… tetapi juga lebih sederhana karena POSIX mengatakan fungsi mana yang aman untuk sinyal asinkron (tidak banyak dari mereka). Perhatikan bahwa header like <pthread.h>memiliki tiga fungsi aman dan banyak fungsi tidak aman.

NB: Hampir semua penilaian fungsi dan header C dalam lingkungan POSIX adalah tebakan semi-terdidik. Tidak ada pernyataan definitif dari badan standar.

Jonathan Leffler
sumber
Mengapa sebagian besar fungsi string dari <string.h>atau fungsi kelas karakter dari <ctype.h>dan banyak lagi fungsi pustaka standar C tidak ada dalam daftar di atas? Implementasi harus sengaja dibuat jahat untuk membuat strlen()panggilan tidak aman dari penangan sinyal.
chqrlie
@chqrlie: pertanyaan menarik - lihat pembaruannya (tidak ada cara untuk memasukkan sebanyak itu ke dalam komentar dengan bijaksana).
Jonathan Leffler
Terima kasih atas analisis mendalam Anda. Mengenai <ctype.h>hal - hal tersebut, ini adalah khusus lokal dan dapat menyebabkan masalah jika sinyal mengganggu fungsi pengaturan lokal, tetapi setelah lokal dimuat, menggunakannya harus aman. Saya kira, dalam beberapa situasi kompleks, memuat data lokal dapat dilakukan secara bertahap, sehingga membuat fungsi menjadi <ctype.h>tidak aman. Kesimpulannya tetap: Jika ragu, abstain.
chqrlie
@chqrlie: Saya setuju bahwa moral dari cerita harus. Jika ragu, abstain . Itu ringkasan yang bagus.
Jonathan Leffler
13

Bagaimana cara menghindari penggunaan printfdalam penangan sinyal?

  1. Selalu menghindarinya, akan berkata: Jangan gunakan printf()di penangan sinyal.

  2. Setidaknya pada sistem sesuai POSIX, Anda dapat menggunakan write(STDOUT_FILENO, ...)bukan printf(). Namun, pemformatan mungkin tidak mudah: Cetak int dari penangan sinyal menggunakan fungsi tulis atau aman asinkron

alk
sumber
1
Alk Always avoid it.artinya? Hindari printf()?
Grijesh Chauhan
2
@GrijeshChauhan: Ya, karena OP bertanya kapan harus menghindari penggunaan printf()dalam penangan sinyal.
alk
Alk +1 untuk 2poin, periksa OP menanyakan Bagaimana cara menghindari penggunaan printf()dalam penangan sinyal?
Grijesh Chauhan
7

Untuk tujuan debugging, saya menulis alat yang memverifikasi bahwa Anda sebenarnya hanya memanggil fungsi-fungsi yang ada di async-signal-safedaftar, dan mencetak pesan peringatan untuk setiap fungsi yang tidak aman yang dipanggil dalam konteks sinyal. Meskipun tidak menyelesaikan masalah keinginan untuk memanggil fungsi non-async-safe dari konteks sinyal, ini setidaknya membantu Anda menemukan kasus di mana Anda melakukannya secara tidak sengaja.

Kode sumber ada di GitHub . Ia bekerja dengan membebani signal/sigaction, kemudian membajak sementara PLTentri dari fungsi yang tidak aman; ini menyebabkan panggilan ke fungsi yang tidak aman dialihkan ke pembungkus.

dwks
sumber
Permintaan fitur GCC: gcc.gnu.org/ml/gcc-help/2012-03/msg00210.html
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1

Salah satu teknik yang sangat berguna dalam program yang memiliki loop pilihan adalah dengan menulis satu byte ke bawah pipa saat menerima sinyal dan kemudian menangani sinyal dalam loop pilih. Sesuatu di sepanjang baris ini (penanganan kesalahan dan detail lainnya dihilangkan agar singkat) :

Jika Anda peduli dengan sinyal yang mana , maka byte di pipa dapat menjadi nomor sinyal.

John Hascall
sumber
1

Terapkan async-signal-safe Anda sendiri snprintf("%ddan gunakanwrite

Ini tidak seburuk yang saya pikirkan, Bagaimana cara mengubah int menjadi string di C? memiliki beberapa implementasi.

Karena hanya ada dua jenis data yang menarik yang dapat diakses oleh penangan sinyal:

  • sig_atomic_t global
  • int argumen sinyal

ini pada dasarnya mencakup semua kasus penggunaan yang menarik.

Fakta bahwa strcpysinyal juga aman membuat segalanya menjadi lebih baik.

Program POSIX di bawah ini mencetak berapa kali ia menerima SIGINT sejauh ini, yang dapat Anda picu dengan Ctrl + C, dan ID sinyal.

Anda dapat keluar dari program dengan Ctrl + \(SIGQUIT).

main.c:

Kompilasi dan jalankan:

Setelah menekan Ctrl + C lima belas kali, terminal menunjukkan:

dimana 2nomor sinyal untuk SIGINT.

Diuji di Ubuntu 18.04. GitHub upstream .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
0

Anda juga dapat menggunakan write()secara langsung yang merupakan fungsi async-signal-safe.

Samuel Rodríguez
sumber
-1

Anda dapat menggunakan printf dalam penangan sinyal jika Anda menggunakan pustaka pthread. unix / posix menetapkan bahwa printf adalah atom untuk utas cf Dave Butenhof balas di sini: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Perhatikan bahwa untuk mendapatkan gambaran yang lebih jelas dari output printf, Anda harus menjalankan aplikasi Anda di konsol (di linux gunakan ctl + alt + f1 untuk memulai konsol 1), bukan pseudo-tty yang dibuat oleh GUI.

drlolly
sumber
3
Penangan sinyal tidak berjalan di beberapa utas terpisah, mereka berjalan dalam konteks utas yang sedang berjalan saat interupsi sinyal terjadi. Jawaban ini sepenuhnya salah.
itaych