Karena printf
bukan reentrant, maka seharusnya tidak aman digunakan dalam penangan sinyal. Tetapi saya telah melihat banyak kode contoh yang menggunakan printf
cara ini.
Jadi pertanyaan saya adalah: kapan kita perlu menghindari penggunaan printf
dalam penangan sinyal, dan apakah ada pengganti yang disarankan?
printf
panggilan itu di penangan sinyal itu? Hapus.Jawaban:
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.Perhatikan dalam contoh di bawah ini, penangan sinyal ding () menetapkan sebuah flag
alarm_fired
ke 1 saat SIGALRM ditangkap dan dalamalarm_fired
nilai fungsi utama diperiksa untuk memanggil printf dengan benar secara kondisional.static int alarm_fired = 0; void ding(int sig) // can be called asynchronously { alarm_fired = 1; // set flag } int main() { pid_t pid; printf("alarm application starting\n"); pid = fork(); switch(pid) { case -1: /* Failure */ perror("fork failed"); exit(1); case 0: /* child */ sleep(5); kill(getppid(), SIGALRM); exit(0); } /* if we get here we are the parent process */ printf("waiting for alarm to go off\n"); (void) signal(SIGALRM, ding); pause(); if (alarm_fired) // check flag to call printf printf("Ding!\n"); printf("done\n"); exit(0); }
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()
atausigaction()
.Daftar fungsi resmi di halaman manual , memanggil fungsi ini di dalam penangan sinyal aman.
sumber
volatile sigatomic_t alarm_fired;
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 dipanggilmalloc()
, ini dapat merusak sepenuhnya manajemen memori.Standar C mengambil pandangan yang sangat konservatif tentang apa yang dapat Anda lakukan di penangan sinyal:
POSIX jauh lebih murah hati tentang apa yang dapat Anda lakukan di penangan sinyal.
Konsep Signal dalam edisi POSIX 2008 mengatakan:
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:_Exit() getppid() sendmsg() tcgetpgrp() _exit() getsockname() sendto() tcsendbreak() abort() getsockopt() setgid() tcsetattr() accept() getuid() setpgid() tcsetpgrp() access() htonl() setsid() time() aio_error() htons() setsockopt() timer_getoverrun() aio_return() kill() setuid() timer_gettime() aio_suspend() link() shutdown() timer_settime() alarm() linkat() sigaction() times() bind() listen() sigaddset() umask() cfgetispeed() longjmp() sigdelset() uname() cfgetospeed() lseek() sigemptyset() unlink() cfsetispeed() lstat() sigfillset() unlinkat() cfsetospeed() memccpy() sigismember() utime() chdir() memchr() siglongjmp() utimensat() chmod() memcmp() signal() utimes() chown() memcpy() sigpause() wait() clock_gettime() memmove() sigpending() waitpid() close() memset() sigprocmask() wcpcpy() connect() mkdir() sigqueue() wcpncpy() creat() mkdirat() sigset() wcscat() dup() mkfifo() sigsuspend() wcschr() dup2() mkfifoat() sleep() wcscmp() execl() mknod() sockatmark() wcscpy() execle() mknodat() socket() wcscspn() execv() ntohl() socketpair() wcslen() execve() ntohs() stat() wcsncat() faccessat() open() stpcpy() wcsncmp() fchdir() openat() stpncpy() wcsncpy() fchmod() pause() strcat() wcsnlen() fchmodat() pipe() strchr() wcspbrk() fchown() poll() strcmp() wcsrchr() fchownat() posix_trace_event() strcpy() wcsspn() fcntl() pselect() strcspn() wcsstr() fdatasync() pthread_kill() strlen() wcstok() fexecve() pthread_self() strncat() wmemchr() ffs() pthread_sigmask() strncmp() wmemcmp() fork() raise() strncpy() wmemcpy() fstat() read() strnlen() wmemmove() fstatat() readlink() strpbrk() wmemset() fsync() readlinkat() strrchr() write() ftruncate() recv() strspn() futimens() recvfrom() strstr() getegid() recvmsg() strtok_r() geteuid() rename() symlink() getgid() renameat() symlinkat() getgroups() rmdir() tcdrain() getpeername() select() tcflow() getpgrp() sem_post() tcflush() getpid() send() tcgetattr()
Akibatnya, Anda akhirnya menggunakan
write()
tanpa dukungan pemformatan yang disediakan olehprintf()
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:
Untuk banyak fungsi di
<string.h>
, sulit untuk melihat mengapa mereka tidak dinyatakan aman async-sinyal, dan saya setuju denganstrlen()
adalah contoh utama, bersama denganstrchr()
,strstr()
, dll Di sisi lain, fungsi lain sepertistrtok()
,strcoll()
danstrxfrm()
agak rumit dan cenderung tidak aman untuk sinyal asinkron. Karenastrtok()
mempertahankan status antar panggilan, dan penangan sinyal tidak dapat dengan mudah membedakan apakah beberapa bagian dari kode yang digunakanstrtok()
akan kacau. Fungsistrcoll()
danstrxfrm()
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 sepertistrcoll()
danstrxfrm()
.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 (tetapitime()
diizinkan secara eksplisit)<uchar.h>
- Bergantung pada lokal<wchar.h>
- Bergantung pada lokal<wctype.h>
- Bergantung pada lokalMenganalisis 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.
sumber
<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 membuatstrlen()
panggilan tidak aman dari penangan sinyal.<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.Selalu menghindarinya, akan berkata: Jangan gunakan
printf()
di penangan sinyal.Setidaknya pada sistem sesuai POSIX, Anda dapat menggunakan
write(STDOUT_FILENO, ...)
bukanprintf()
. Namun, pemformatan mungkin tidak mudah: Cetak int dari penangan sinyal menggunakan fungsi tulis atau aman asinkronsumber
Always avoid it.
artinya? Hindariprintf()
?printf()
dalam penangan sinyal.2
poin, periksa OP menanyakan Bagaimana cara menghindari penggunaanprintf()
dalam penangan sinyal?Untuk tujuan debugging, saya menulis alat yang memverifikasi bahwa Anda sebenarnya hanya memanggil fungsi-fungsi yang ada di
async-signal-safe
daftar, 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 sementaraPLT
entri dari fungsi yang tidak aman; ini menyebabkan panggilan ke fungsi yang tidak aman dialihkan ke pembungkus.sumber
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) :
static int sigPipe[2]; static void gotSig ( int num ) { write(sigPipe[1], "!", 1); } int main ( void ) { pipe(sigPipe); /* use sigaction to point signal(s) at gotSig() */ FD_SET(sigPipe[0], &readFDs); for (;;) { n = select(nFDs, &readFDs, ...); if (FD_ISSET(sigPipe[0], &readFDs)) { read(sigPipe[0], ch, 1); /* do something about the signal here */ } /* ... the rest of your select loop */ } }
Jika Anda peduli dengan sinyal yang mana , maka byte di pipa dapat menjadi nomor sinyal.
sumber
Terapkan async-signal-safe Anda sendiri
snprintf("%d
dan 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
globalint
argumen sinyalini pada dasarnya mencakup semua kasus penggunaan yang menarik.
Fakta bahwa
strcpy
sinyal 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:
#define _XOPEN_SOURCE 700 #include <assert.h> #include <limits.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> /* Calculate the minimal buffer size for a given type. * * Here we overestimate and reserve 8 chars per byte. * * With this size we could even print a binary string. * * - +1 for NULL terminator * - +1 for '-' sign * * A tight limit for base 10 can be found at: * /programming/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108 * * TODO: get tight limits for all bases, possibly by looking into * glibc's atoi: /programming/190229/where-is-the-itoa-function-in-linux/52127877#52127877 */ #define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2 /* async-signal-safe implementation of integer to string conversion. * * Null terminates the output string. * * The input buffer size must be large enough to contain the output, * the caller must calculate it properly. * * @param[out] value Input integer value to convert. * @param[out] result Buffer to output to. * @param[in] base Base to convert to. * @return Pointer to the end of the written string. */ char *itoa_safe(intmax_t value, char *result, int base) { intmax_t tmp_value; char *ptr, *ptr2, tmp_char; if (base < 2 || base > 36) { return NULL; } ptr = result; do { tmp_value = value; value /= base; *ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)]; } while (value); if (tmp_value < 0) *ptr++ = '-'; ptr2 = result; result = ptr; *ptr-- = '\0'; while (ptr2 < ptr) { tmp_char = *ptr; *ptr--= *ptr2; *ptr2++ = tmp_char; } return result; } volatile sig_atomic_t global = 0; void signal_handler(int sig) { char key_str[] = "count, sigid: "; /* This is exact: * - the null after the first int will contain the space * - the null after the second int will contain the newline */ char buf[2 * ITOA_SAFE_STRLEN(sig_atomic_t) + sizeof(key_str)]; enum { base = 10 }; char *end; end = buf; strcpy(end, key_str); end += sizeof(key_str); end = itoa_safe(global, end, base); *end++ = ' '; end = itoa_safe(sig, end, base); *end++ = '\n'; write(STDOUT_FILENO, buf, end - buf); global += 1; signal(sig, signal_handler); } int main(int argc, char **argv) { /* Unit test itoa_safe. */ { typedef struct { intmax_t n; int base; char out[1024]; } InOut; char result[1024]; size_t i; InOut io; InOut ios[] = { /* Base 10. */ {0, 10, "0"}, {1, 10, "1"}, {9, 10, "9"}, {10, 10, "10"}, {100, 10, "100"}, {-1, 10, "-1"}, {-9, 10, "-9"}, {-10, 10, "-10"}, {-100, 10, "-100"}, /* Base 2. */ {0, 2, "0"}, {1, 2, "1"}, {10, 2, "1010"}, {100, 2, "1100100"}, {-1, 2, "-1"}, {-100, 2, "-1100100"}, /* Base 35. */ {0, 35, "0"}, {1, 35, "1"}, {34, 35, "Y"}, {35, 35, "10"}, {100, 35, "2U"}, {-1, 35, "-1"}, {-34, 35, "-Y"}, {-35, 35, "-10"}, {-100, 35, "-2U"}, }; for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) { io = ios[i]; itoa_safe(io.n, result, io.base); if (strcmp(result, io.out)) { printf("%ju %d %s\n", io.n, io.base, io.out); assert(0); } } } /* Handle the signals. */ if (argc > 1 && !strcmp(argv[1], "1")) { signal(SIGINT, signal_handler); while(1); } return EXIT_SUCCESS; }
Kompilasi dan jalankan:
gcc -std=c99 -Wall -Wextra -o main main.c ./main 1
Setelah menekan Ctrl + C lima belas kali, terminal menunjukkan:
^Ccount, sigid: 0 2 ^Ccount, sigid: 1 2 ^Ccount, sigid: 2 2 ^Ccount, sigid: 3 2 ^Ccount, sigid: 4 2 ^Ccount, sigid: 5 2 ^Ccount, sigid: 6 2 ^Ccount, sigid: 7 2 ^Ccount, sigid: 8 2 ^Ccount, sigid: 9 2 ^Ccount, sigid: 10 2 ^Ccount, sigid: 11 2 ^Ccount, sigid: 12 2 ^Ccount, sigid: 13 2 ^Ccount, sigid: 14 2
dimana
2
nomor sinyal untukSIGINT
.Diuji di Ubuntu 18.04. GitHub upstream .
sumber
Anda juga dapat menggunakan
write()
secara langsung yang merupakan fungsi async-signal-safe.#include <unistd.h> int main(void) { write(1,"Hello World!", 12); return 0; }
sumber
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.
sumber