Cara menyebarkan sinyal ke proses anak dari skrip bash

Cara menyebarkan sinyal ke proses anak dari skrip bash

Misalkan kita menulis skrip yang melahirkan satu atau lebih proses berjalan lama; Jika skrip tersebut menerima sinyal seperti Sigint atau Sigterm, Kami mungkin ingin anak -anaknya diberhentikan juga (biasanya ketika orang tua meninggal, anak -anak bertahan). Kami mungkin juga ingin melakukan beberapa tugas pembersihan sebelum skrip itu sendiri keluar. Untuk dapat mencapai tujuan kami, kami harus terlebih dahulu belajar tentang kelompok proses dan cara menjalankan proses di latar belakang.

Dalam tutorial ini Anda akan belajar:

  • Apa itu kelompok proses
  • Perbedaan antara proses latar depan dan latar belakang
  • Cara menjalankan program di latar belakang
  • Cara menggunakan shell Tunggu Dibangun untuk menunggu proses yang dieksekusi di latar belakang
  • Cara menghentikan proses anak ketika orang tua menerima sinyal
Cara menyebarkan sinyal ke proses anak dari skrip bash

Persyaratan dan konvensi perangkat lunak yang digunakan

Persyaratan Perangkat Lunak dan Konvensi Baris Perintah Linux
Kategori Persyaratan, konvensi atau versi perangkat lunak yang digunakan
Sistem Distribusi Independen
Perangkat lunak Tidak ada perangkat lunak khusus yang dibutuhkan
Lainnya Tidak ada
Konvensi # - mensyaratkan perintah linux yang diberikan untuk dieksekusi dengan hak istimewa root baik secara langsung sebagai pengguna root atau dengan menggunakan sudo memerintah
$ - mensyaratkan perintah Linux yang diberikan untuk dieksekusi sebagai pengguna biasa

Contoh sederhana

Mari kita buat skrip yang sangat sederhana dan simulasikan peluncuran proses berjalan lama:

#!/Bin/Bash Trap "Sinyal gema diterima!"Sigint Echo" skrip pid adalah $ "sleep 30 
Menyalin

Hal pertama yang kami lakukan dalam naskah adalah membuat jebakan untuk ditangkap Sigint dan cetak pesan saat sinyal diterima. Kami daripada membuat skrip kami mencetaknya pid: Kita bisa mendapatkan dengan memperluas $$ variabel. Selanjutnya, kami mengeksekusi tidur perintah untuk mensimulasikan proses berjalan yang lama (30 detik).

Kami menyimpan kode di dalam file (katakan itu dipanggil tes.SH), membuatnya dapat dieksekusi, dan meluncurkannya dari emulator terminal. Kami mendapatkan hasil berikut:

Skrip PID adalah 101248 

Jika kita fokus pada emulator terminal dan tekan Ctrl+C saat skrip sedang berjalan, a Sigint sinyal dikirim dan ditangani oleh kami perangkap:

Skrip PID adalah 101248 ^CSignal yang diterima! 

Meskipun jebakan menangani sinyal seperti yang diharapkan, naskah itu terputus. Mengapa ini terjadi? Selanjutnya, jika kami mengirim a Sigint sinyal ke skrip menggunakan membunuh perintah, hasil yang kami peroleh sangat berbeda: jebakan tidak segera dieksekusi, dan skrip berlangsung sampai proses anak tidak keluar (setelah 30 detik "tidur"). Mengapa perbedaan ini? Mari kita lihat…

Kelompok proses, latar depan dan pekerjaan latar belakang

Sebelum kita menjawab pertanyaan di atas, kita harus lebih memahami konsep kelompok proses.

Grup proses adalah sekelompok proses yang memiliki hal yang sama PGID (Proses ID Grup). Ketika anggota kelompok proses menciptakan proses anak, proses itu menjadi anggota dari kelompok proses yang sama. Setiap kelompok proses memiliki pemimpin; kita dapat dengan mudah mengenalinya karena itu pid dan PGID adalah sama.

Kami dapat memvisualisasikan pid Dan PGID menjalankan proses menggunakan ps memerintah. Output dari perintah dapat disesuaikan sehingga hanya bidang yang kami minati ditampilkan: dalam hal ini Cmd, Pid Dan PGID. Kami melakukan ini dengan menggunakan -Hai opsi, memberikan daftar bidang yang dipisahkan secara koma sebagai argumen:

$ ps -a -o pid, pgid, cmd 

Jika kami menjalankan perintah saat skrip kami menjalankan bagian yang relevan dari output yang kami peroleh adalah sebagai berikut:

 PID PGID CMD 298349 298349 /BIN /BASH ./tes.SH 298350 298349 Tidur 30 

Kita dapat dengan jelas melihat dua proses: pid yang pertama adalah 298349, Sama seperti itu PGID: ini adalah pemimpin kelompok proses. Itu dibuat ketika kami meluncurkan skrip seperti yang Anda lihat di Cmd kolom.

Proses utama ini meluncurkan proses anak dengan perintah tidur 30: Seperti yang diharapkan kedua proses berada dalam kelompok proses yang sama.

Ketika kami menekan Ctrl-C sambil berfokus pada terminal dari mana skrip diluncurkan, sinyal tidak hanya dikirim ke proses induk, tetapi ke seluruh kelompok proses. Grup proses mana? Itu Grup proses latar depan terminal. Semua Proses Anggota Grup ini dipanggil proses latar depan, Semua yang lain dipanggil proses latar belakang. Inilah yang dikatakan Bash Manual tentang masalah ini:

TAHUKAH KAMU?Untuk memfasilitasi implementasi antarmuka pengguna ke kontrol pekerjaan, sistem operasi mempertahankan gagasan ID grup proses terminal saat ini. Anggota Grup Proses ini (Prosesnya ID Grup Prosesnya sama dengan ID Grup Proses Terminal Saat Ini) Menerima sinyal yang dihasilkan keyboard seperti SIGINT. Proses -proses ini dikatakan berada di latar depan. Proses latar belakang adalah mereka yang ID grup prosesnya berbeda dari terminal; Proses seperti itu kebal terhadap sinyal yang dihasilkan keyboard.

Saat kami mengirim Sigint sinyal dengan membunuh Perintah, sebaliknya, kami hanya menargetkan PID dari proses induk; Bash menunjukkan perilaku tertentu ketika sinyal diterima saat menunggu program selesai: "kode perangkap" untuk sinyal itu tidak dieksekusi sampai proses itu selesai. Inilah sebabnya mengapa pesan "sinyal diterima" ditampilkan hanya setelah tidur perintah keluar.

Untuk mereplikasi apa yang terjadi ketika kita menekan ctrl-c di terminal menggunakan membunuh Perintah untuk mengirim sinyal, kita harus menargetkan grup proses. Kami dapat mengirim sinyal ke grup proses dengan menggunakan negasi pid pemimpin proses, Jadi, seandainya pid pemimpin prosesnya 298349 (Seperti pada contoh sebelumnya), kami akan menjalankan:

$ kill -2 -298349 

Kelola propagasi sinyal dari dalam skrip

Sekarang, misalkan kita meluncurkan skrip yang sudah berjalan lama dari shell non -interaktif, dan kami ingin skrip tersebut untuk mengelola perambatan sinyal secara otomatis, sehingga ketika menerima sinyal seperti Sigint atau Sigterm itu mengakhiri anaknya yang berpotensi lama, akhirnya melakukan beberapa tugas pembersihan sebelum keluar. Bagaimana kita bisa melakukan ini?

Seperti yang kami lakukan sebelumnya, kami dapat menangani situasi di mana sinyal diterima dalam perangkap; Namun, seperti yang kita lihat, jika sinyal diterima saat shell menunggu program selesai, "kode perangkap" dieksekusi hanya setelah proses anak keluar.

Ini bukan yang kami inginkan: kami ingin kode perangkap diproses segera setelah proses induk menerima sinyal. Untuk mencapai tujuan kami, kami harus melaksanakan proses anak di latar belakang: kita bisa melakukan ini dengan menempatkan & simbol setelah perintah. Dalam kasus kami, kami akan menulis:

#!/Bin/Bash Trap 'sinyal gema diterima!'Sigint echo "skrip pid adalah $" sleep 30 & 
Menyalin

Jika kita akan meninggalkan skrip dengan cara ini, proses induk akan keluar tepat setelah eksekusi tidur 30 perintah, meninggalkan kami tanpa kesempatan untuk melakukan tugas pembersihan setelah berakhir atau terganggu. Kita dapat menyelesaikan masalah ini dengan menggunakan shell Tunggu dibangun. Halaman Bantuan Tunggu mendefinisikannya dengan cara ini:



Menunggu setiap proses yang diidentifikasi oleh ID, yang mungkin merupakan ID proses atau spesifikasi pekerjaan, dan melaporkan status penghentiannya. Jika ID tidak diberikan, tunggu semua proses anak yang aktif saat ini, dan status pengembalian nol.

Setelah kami menetapkan proses untuk dieksekusi di latar belakang, kami dapat mengambilnya pid dalam $! variabel. Kita bisa meneruskannya sebagai argumen Tunggu Untuk membuat proses induk menunggu anaknya:

#!/Bin/Bash Trap 'sinyal gema diterima!'Sigint echo "skrip pid adalah $" sleep 30 & tunggu $! 
Menyalin

Sudahkah kita selesai? Tidak, masih ada masalah: penerimaan sinyal yang ditangani dalam perangkap di dalam naskah, menyebabkan Tunggu Builtin untuk segera kembali, tanpa benar -benar menunggu penghentian perintah di latar belakang. Perilaku ini didokumentasikan dalam manual bash:

Ketika Bash sedang menunggu perintah asinkron melalui builtin menunggu, penerimaan sinyal di mana perangkap telah ditetapkan akan menyebabkan builtin menunggu segera kembali dengan status keluar lebih besar dari 128, segera setelah itu perangkap dieksekusi. Ini bagus, karena sinyal ditangani segera dan jebakan dieksekusi tanpa harus menunggu anak untuk berakhir, tetapi menimbulkan masalah, karena dalam perangkap kami, kami ingin menjalankan tugas pembersihan kami hanya setelah kami yakin anak tersebut proses keluar.

Untuk menyelesaikan masalah ini yang harus kita gunakan Tunggu Sekali lagi, mungkin sebagai bagian dari jebakan itu sendiri. Inilah yang terlihat seperti skrip kami pada akhirnya:

#!/bin/bash cleanup () echo "cleaning up…" # kode pembersihan kami pergi ke sini trap 'sinyal gema diterima!; kill "$ child_pid"; tunggu "$ child_pid"; cleanup 'sigint sigterm echo "skrip pid adalah $" sleep 30 & child_pid = "$!"tunggu" $ child_pid " 
Menyalin

Dalam skrip kami membuat a membersihkan fungsi di mana kami dapat memasukkan kode pembersihan kami, dan membuat kami perangkap Tangkap juga Sigterm sinyal. Inilah yang terjadi ketika kita menjalankan skrip ini dan mengirim salah satu dari dua sinyal untuk itu:

  1. Skrip diluncurkan dan tidur 30 Perintah dieksekusi di latar belakang;
  2. Itu pid dari proses anak "disimpan" di Child_pid variabel;
  3. Script menunggu penghentian proses anak;
  4. Skrip menerima Sigint atau Sigterm sinyal
  5. Itu Tunggu Komando segera kembali, tanpa menunggu pemutusan anak;

Pada titik ini jebakan dieksekusi. Di dalamnya:

  1. A Sigterm sinyal ( membunuh default) dikirim ke Child_pid;
  2. Kami Tunggu untuk memastikan anak diberhentikan setelah menerima sinyal ini.
  3. Setelah Tunggu kembali, kami menjalankan membersihkan fungsi.

Menyebarkan sinyal ke banyak anak

Dalam contoh di atas kami bekerja dengan skrip yang hanya memiliki satu proses anak. Bagaimana jika sebuah naskah memiliki banyak anak, dan bagaimana jika beberapa dari mereka memiliki anak sendiri?

Dalam kasus pertama, satu cara cepat untuk mendapatkan PIDS dari semua anak adalah menggunakan pekerjaan -p Perintah: Perintah ini menampilkan PID dari semua pekerjaan aktif di shell saat ini. Kami bisa daripada menggunakan membunuh untuk mengakhiri mereka. Inilah contohnya:

#!/bin/bash cleanup () echo "cleaning up…" # kode pembersihan kami pergi ke sini trap 'sinyal gema diterima!; Bunuh $ (pekerjaan -p); Tunggu; Cleanup 'sigint sigterm echo "skrip pid adalah $" sleep 30 & sleep 40 & tunggu 
Menyalin

Script meluncurkan dua proses di latar belakang: dengan menggunakan Tunggu dibangun tanpa argumen, kami menunggu semuanya, dan menjaga proses induk tetap hidup. Ketika Sigint atau Sigterm Sinyal diterima oleh skrip, kami mengirim a Sigterm Bagi mereka berdua, pids mereka dikembalikan oleh pekerjaan -p memerintah (pekerjaan itu sendiri merupakan shell built-in, jadi ketika kita menggunakannya, proses baru tidak dibuat).

Jika anak -anak memiliki anak -anak proses mereka sendiri, dan kami ingin menghentikan mereka semua ketika leluhur menerima sinyal, kami dapat mengirim sinyal ke seluruh kelompok proses, seperti yang kita lihat sebelumnya.

Ini, bagaimanapun, menghadirkan masalah, karena dengan mengirimkan sinyal terminasi ke kelompok proses, kami akan memasukkan loop "Sinyal-Sent/Signal Trapped". Pikirkan tentang hal ini: di perangkap untuk Sigterm kami mengirim a Sigterm sinyal untuk semua anggota kelompok proses; Ini termasuk skrip induk itu sendiri!

Untuk mengatasi masalah ini dan masih dapat menjalankan fungsi pembersihan setelah proses anak diakhiri, kita harus mengubah perangkap untuk Sigterm Tepat sebelum kami mengirim sinyal ke grup proses, misalnya:

#!/bin/bash cleanup () echo "cleaning up ..." # Kode pembersihan kami pergi ke sini jebakan 'jebakan "" sigterm; Bunuh 0; Tunggu; Cleanup 'sigint sigterm echo "skrip pid adalah $" sleep 30 & sleep 40 & tunggu 
Menyalin

Di dalam perangkap, sebelum mengirim Sigterm Ke grup proses, kami mengubah Sigterm jebakan, sehingga proses induk mengabaikan sinyal dan hanya keturunannya yang terpengaruh olehnya. Perhatikan juga bahwa dalam perangkap, untuk memberi sinyal kelompok proses, kami gunakan membunuh dengan 0 sebagai pid. Ini adalah semacam jalan pintas: saat pid diteruskan ke membunuh adalah 0, semua proses di saat ini Grup proses ditandai.

Kesimpulan

Dalam tutorial ini kami belajar tentang kelompok proses dan apa perbedaan antara proses latar depan dan latar belakang. Kami belajar bahwa Ctrl-C mengirimkan a Sigint Sinyal ke seluruh kelompok proses latar depan terminal pengendali, dan kami belajar cara mengirim sinyal ke kelompok proses menggunakan membunuh. Kami juga belajar cara menjalankan program di latar belakang, dan cara menggunakan Tunggu shell built in untuk menunggu agar keluar tanpa kehilangan cangkang induk. Akhirnya, kami melihat cara mengatur skrip sehingga ketika menerima sinyal, ia mengakhiri anak -anaknya sebelum keluar. Apakah saya merindukan sesuatu? Apakah Anda memiliki resep pribadi untuk menyelesaikan tugas? Jangan ragu untuk memberi tahu saya!

Tutorial Linux Terkait:

  • Manajemen proses latar belakang bash
  • Multi-threaded Bash Scripting & Manajemen Proses di…
  • Pengantar Otomatisasi Linux, Alat dan Teknik
  • Cara menjalankan perintah di latar belakang di linux
  • Menguasai loop skrip bash
  • Cara Menginstal Sinyal di Linux
  • Cara melacak panggilan sistem yang dilakukan oleh proses dengan strace di…
  • Mint 20: Lebih baik dari Ubuntu dan Microsoft Windows?
  • Membandingkan Linux Apache Prefork vs Pekerja MPM
  • Cara bekerja dengan grup paket DNF