Masalah utama adalah jika sinyal mengganggu malloc()
atau beberapa fungsi serupa, keadaan internal mungkin sementara tidak konsisten saat memindahkan blok memori antara daftar bebas dan bekas, atau operasi serupa lainnya. Jika kode dalam penangan sinyal memanggil fungsi yang kemudian memanggil malloc()
, ini benar-benar dapat merusak manajemen memori.
Standar C mengambil pandangan yang sangat konservatif tentang apa yang dapat Anda lakukan dalam penangan sinyal:
ISO/IEC 9899:2011 §7.14.1.1
signal
fungsi¶5 Jika sinyal terjadi selain sebagai akibat pemanggilan
abort
atauraise
fungsi, perilaku tidak terdefinisi jika penangan sinyal merujuk ke objek apa pun dengan durasi penyimpanan statis atau thread yang bukan objek atom bebas kunci selain dengan menetapkan nilai ke objek yang dinyatakan sebagaivolatile sig_atomic_t
, atau penangan sinyal memanggil fungsi apa pun di pustaka standar selainabort
fungsi,_Exit
fungsi,quick_exit
fungsi, atausignal
fungsi dengan argumen pertama sama dengan nomor sinyal yang sesuai dengan sinyal yang menyebabkan pemanggilan handler. Selanjutnya, jika seperti panggilan kesignal
fungsi menghasilkanSIG_ERR
return, nilaierrno
tidak dapat ditentukan.Jika ada sinyal yang dihasilkan oleh penangan sinyal asinkron, perilakunya tidak ditentukan.
POSIX jauh lebih bermurah hati tentang apa yang dapat Anda lakukan dalam penangan sinyal.
Konsep Sinyal dalam edisi POSIX 2008 mengatakan:
Jika prosesnya multi-threaded, atau jika prosesnya single-threaded dan penangan sinyal dijalankan selain sebagai hasil dari:
Proses memanggil
abort()
,raise()
,kill()
,pthread_kill()
, atausigqueue()
untuk menghasilkan sinyal yang tidak diblokirSinyal yang tertunda sedang dibuka blokirnya dan dikirim sebelum panggilan yang membuka blokirnya dikembalikan
perilaku tidak terdefinisi jika penangan sinyal merujuk ke objek apa pun selain
errno
dengan durasi penyimpanan statis selain dengan menetapkan nilai ke objek yang dideklarasikan sebagaivolatile sig_atomic_t
, atau jika penangan sinyal memanggil fungsi apa pun yang ditentukan dalam standar ini selain dari salah satu fungsi yang tercantum dalam tabel berikut.Tabel berikut menentukan serangkaian fungsi yang harus aman untuk sinyal asinkron. Oleh karena itu, aplikasi dapat memanggilnya, tanpa batasan, dari fungsi penangkap sinyal:
_Exit() fexecve() posix_trace_event() sigprocmask() _exit() fork() pselect() sigqueue() … fcntl() pipe() sigpause() write() fdatasync() poll() sigpending()
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 ditentukan saat dipanggil dari atau diinterupsi oleh fungsi penangkap sinyal, dengan pengecualian tunggal:saat sinyal menginterupsi fungsi yang tidak aman dan sinyal- catching memanggil fungsi yang tidak aman, perilakunya tidak ditentukan.
Operasi yang mendapatkan nilai
errno
dan operasi yang menetapkan nilai keerrno
harus async-signal-safe.Saat sinyal dikirimkan ke utas, jika tindakan sinyal tersebut menentukan penghentian, penghentian, atau kelanjutan, seluruh proses akan dihentikan, dihentikan, atau dilanjutkan, masing-masing.
Namun, printf()
kumpulan fungsi tidak ada dalam daftar tersebut dan mungkin tidak dapat dipanggil dengan aman dari penangan sinyal.
POSIX 2016 update memperluas daftar fungsi aman untuk menyertakan, khususnya, sejumlah besar fungsi dari <string.h>
, yang merupakan tambahan yang sangat berharga (atau merupakan pengawasan yang sangat membuat frustrasi). 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 oleh printf()
et al, atau Anda akhirnya menyetel bendera yang Anda uji (secara berkala) di tempat yang sesuai dalam kode Anda. Teknik ini dengan cakap didemonstrasikan dalam jawaban oleh Grijesh Chauhan.
Fungsi standar C dan keamanan sinyal
chqrlie mengajukan pertanyaan menarik, yang saya tidak punya lebih dari sebagian jawabannya:
Kenapa kebanyakan fungsi string dari
<string.h>
atau fungsi kelas karakter dari<ctype.h>
dan masih banyak lagi fungsi pustaka standar C yang tidak ada dalam daftar di atas? Implementasi harus sengaja dibuat jahat untuk membuatstrlen()
tidak aman untuk menelepon dari penangan sinyal.
Untuk banyak fungsi di <string.h>
, sulit untuk melihat mengapa mereka tidak dinyatakan sebagai async-signal safe, 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 sepertinya tidak aman untuk sinyal asinkron. Karena strtok()
mempertahankan status di antara panggilan, dan penangan sinyal tidak dapat dengan mudah mengetahui apakah ada bagian dari kode yang menggunakan strtok()
akan kacau. strcoll()
dan strxfrm()
fungsi bekerja dengan data sensitif-lokal, dan memuat lokal melibatkan semua jenis pengaturan status.
Fungsi (makro) dari <ctype.h>
semuanya sensitif terhadap lokal, dan karena itu dapat mengalami masalah yang sama seperti strcoll()
dan strxfrm()
.
Saya merasa sulit untuk melihat mengapa fungsi matematika dari <math.h>
tidak aman untuk sinyal asinkron, kecuali karena mereka dapat dipengaruhi oleh SIGFPE (pengecualian floating point), meskipun satu-satunya saat saya melihat salah satunya 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. Lainnya secara khusus bermasalah:malloc()
dan keluarga adalah contoh utama.
Penilaian serupa dapat dibuat untuk header lain di Standar C (2011) yang digunakan di lingkungan POSIX. (Standar C sangat terbatas sehingga tidak ada minat untuk menganalisisnya di lingkungan Standar C murni.) Yang ditandai 'bergantung pada 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 diizinkan; 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()
secara eksplisit diizinkan)<uchar.h>
— Bergantung pada lokal<wchar.h>
— Bergantung pada lokal<wctype.h>
— Bergantung pada lokal
Menganalisis header POSIX akan … lebih sulit karena jumlahnya banyak, dan beberapa fungsi mungkin aman tetapi banyak yang tidak … tetapi juga lebih sederhana karena POSIX mengatakan fungsi mana yang aman dengan sinyal asinkron (tidak banyak). Perhatikan bahwa tajuk seperti <pthread.h>
memiliki tiga fungsi aman dan banyak fungsi tidak aman.
NB: Hampir semua penilaian fungsi dan header C di lingkungan POSIX adalah tebakan semi-terpelajar. Tidak masuk akal pernyataan definitif dari badan standar.
Cara menghindari penggunaan
printf
dalam pengendali sinyal?
-
Selalu hindari, akan berkata:Jangan gunakan
printf()
di penangan sinyal. -
Setidaknya pada sistem yang sesuai dengan POSIX, Anda dapat menggunakan
write(STDOUT_FILENO, ...)
bukannyaprintf()
. Namun pemformatan mungkin tidak mudah:Print int dari signal handler menggunakan fungsi write atau async-safe
Anda dapat menggunakan beberapa variabel flag, menyetel flag tersebut di dalam penangan sinyal, dan berdasarkan panggilan flag printf()
fungsi di main() atau bagian lain dari program selama operasi normal.
Tidaklah aman untuk memanggil semua fungsi, seperti
printf
, dari dalam penangan sinyal. Teknik yang berguna adalah menggunakan penangan sinyal untuk menyetelflag
lalu periksaflag
itu dari program utama dan cetak pesan jika diperlukan.
Perhatikan pada contoh di bawah ini, signal handler ding() menetapkan flag alarm_fired
ke 1 saat SIGALRM tertangkap dan dalam fungsi utama alarm_fired
nilai diperiksa untuk memanggil printf secara kondisional dengan benar.
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 dijelaskan kode Anda (apa yang Anda inginkan), Bab 11:Proses dan Sinyal, halaman 484
Selain itu, Anda perlu berhati-hati dalam menulis fungsi handler karena dapat dipanggil secara asinkron. Artinya, seorang pawang dapat dipanggil kapan saja di dalam program, tidak dapat diprediksi. Jika dua sinyal tiba dalam interval yang sangat singkat, satu penangan dapat berjalan di dalam yang lain. Dan dianggap praktik yang lebih baik untuk mendeklarasikan volatile sigatomic_t
, tipe ini selalu diakses secara atomik, hindari ketidakpastian tentang interupsi akses ke variabel. (baca:Akses Data Atom dan Penanganan Sinyal untuk penegasan detail).
Baca Mendefinisikan Penangan Sinyal :untuk mempelajari cara menulis fungsi penangan sinyal yang dapat dibuat dengan signal()
atau sigaction()
fungsi.
Daftar fungsi resmi di halaman manual, memanggil fungsi ini di dalam penangan sinyal aman.