GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana cara menghindari penggunaan printf dalam penangan sinyal?

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 atau raise 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 sebagai volatile sig_atomic_t , atau penangan sinyal memanggil fungsi apa pun di pustaka standar selain abort fungsi, _Exit fungsi, quick_exit fungsi, atau signal fungsi dengan argumen pertama sama dengan nomor sinyal yang sesuai dengan sinyal yang menyebabkan pemanggilan handler. Selanjutnya, jika seperti panggilan ke signal fungsi menghasilkan SIG_ERR return, nilai errno 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() , atau sigqueue() untuk menghasilkan sinyal yang tidak diblokir

  • Sinyal 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 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 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 ke errno 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 membuat strlen() 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 (tetapi time() 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?

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

  2. Setidaknya pada sistem yang sesuai dengan POSIX, Anda dapat menggunakan write(STDOUT_FILENO, ...) bukannya printf() . 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 menyetel flag lalu periksa flag 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.


Linux
  1. Bagaimana cara menulis penangan sinyal untuk menangkap SIGSEGV?

  2. Antrian sinyal di C

  3. C++11:Bagaimana cara membuat alias suatu fungsi?

  1. Bagaimana cara menulis integer ke file biner menggunakan Bash?

  2. IPC menggunakan Sinyal di linux

  3. Bagaimana cara menghindari pesan peringatan 'adalah file yang sama' saat menggunakan cp di Linux?

  1. Bash - Cara mencetak string multi baris (dengan '\n') menggunakan printf

  2. Bagaimana cara menghindari petunjuk saat menggunakan azcopy di Linux dalam skrip?

  3. Bagaimana cara menghapus file tanpa menggunakan rm?