Mengingat proses shell (mis. sh
) Dan proses turunannya (mis cat
), bagaimana saya bisa mensimulasikan perilaku Ctrl+ Cmenggunakan ID proses shell?
Inilah yang saya coba:
Berjalan sh
lalu cat
:
[user@host ~]$ sh
sh-4.3$ cat
test
test
Mengirim SIGINT
ke cat
dari terminal lain:
[user@host ~]$ kill -SIGINT $PID_OF_CAT
cat
menerima sinyal dan diakhiri (seperti yang diharapkan).
Mengirim sinyal ke proses induk tampaknya tidak berfungsi. Mengapa sinyal tidak disebarkan ke cat
saat dikirim ke proses induknya sh
?
Ini tidak bekerja:
[user@host ~]$ kill -SIGINT $PID_OF_SH
Jawaban:
Cara CTRL+ Cbekerja
Hal pertama adalah memahami cara CTRL+ Cbekerja.
Ketika Anda menekan CTRL+ C, emulator terminal Anda mengirimkan karakter ETX (akhir teks / 0x03).
TTY dikonfigurasi sedemikian rupa sehingga ketika menerima karakter ini, ia mengirimkan SIGINT ke grup proses foreground terminal. Konfigurasi ini dapat dilihat dengan melakukan
stty
dan melihatintr = ^C;
. The spesifikasi POSIX mengatakan bahwa ketika INTR diterima, harus mengirim SIGINT untuk kelompok proses latar depan terminal itu.Apa kelompok proses latar depan?
Jadi, sekarang pertanyaannya adalah, bagaimana Anda menentukan apa grup proses latar depan? Grup proses latar depan hanyalah grup proses yang akan menerima sinyal apa pun yang dihasilkan oleh keyboard (SIGTSTOP, SIGINT, dll.).
Cara termudah untuk menentukan ID grup proses adalah dengan menggunakan
ps
:Kolom kedua akan menjadi ID grup proses.
Bagaimana cara mengirim sinyal ke grup proses?
Sekarang kita tahu apa ID grup proses, kita perlu mensimulasikan perilaku POSIX mengirim sinyal ke seluruh grup.
Ini dapat dilakukan dengan
kill
meletakkan tanda-
didepan ID grup.Misalnya, jika ID grup proses Anda 1234, Anda akan menggunakan:
Simulasikan CTRL+ Cmenggunakan nomor terminal.
Jadi di atas mencakup cara mensimulasikan CTRL+ Csebagai proses manual. Tetapi bagaimana jika Anda tahu nomor TTY, dan Anda ingin mensimulasikan CTRL+ Cuntuk terminal itu?
Ini menjadi sangat mudah.
Mari kita asumsikan
$tty
terminal yang ingin Anda targetkan (Anda bisa mendapatkannya dengan menjalankannyatty | sed 's#^/dev/##'
di terminal).Ini akan mengirimkan SIGINT ke grup proses foreground apa pun
$tty
.sumber
kill
perintah mungkin gagal.sends a SIGINT to the foreground process group of the terminal.
fork
. Minimal runnable C misalnya di: unix.stackexchange.com/a/465112/32558Seperti yang dikatakan vinc17, tidak ada alasan untuk hal ini terjadi. Saat Anda mengetik urutan kunci penghasil sinyal (mis., Ctrl+ C), Sinyal dikirim ke semua proses yang terhubung ke (terkait dengan) terminal. Tidak ada mekanisme untuk sinyal yang dihasilkan oleh
kill
.Namun, perintahnya seperti
akan mengirim sinyal ke semua proses dalam grup proses 12345; lihat membunuh (1) dan membunuh (2) . Anak-anak dari shell biasanya berada dalam grup proses shell (setidaknya, jika mereka tidak sinkron), jadi mengirimkan sinyal ke negatif PID shell dapat melakukan apa yang Anda inginkan.
Ups
Seperti yang ditunjukkan vinc17, ini tidak berfungsi untuk shell interaktif. Inilah alternatif yang mungkin berhasil:
ps -pPID_of_shell
mendapat informasi proses pada shell.o tpgid=
memberitahups
untuk menampilkan hanya ID grup proses terminal, tanpa header. Jika ini kurang dari 10.000,ps
akan menampilkannya dengan ruang terdepan; yang$(echo …)
adalah trik cepat untuk menanggalkan terkemuka (dan trailing) spasi.Saya mendapatkan ini bekerja dalam pengujian sepintas pada mesin Debian.
sumber
Pertanyaannya berisi jawabannya sendiri. Mengirimkan
SIGINT
kecat
proses dengankill
adalah simulasi sempurna dari apa yang terjadi ketika Anda menekan^C
.Untuk lebih tepatnya, karakter interupsi (
^C
secara default) mengirimSIGINT
ke setiap proses dalam grup proses latar depan terminal. Jika alih-alihcat
Anda menjalankan perintah yang lebih rumit yang melibatkan banyak proses, Anda harus membunuh grup proses untuk mencapai efek yang sama dengan^C
.Ketika Anda menjalankan perintah eksternal apa pun tanpa
&
operator latar belakang, shell membuat grup proses baru untuk perintah dan memberi tahu terminal bahwa grup proses ini sekarang berada di latar depan. Shell masih dalam grup prosesnya sendiri, yang tidak lagi berada di latar depan. Kemudian shell menunggu perintah untuk keluar.Di situlah Anda tampaknya telah menjadi korban oleh kesalahpahaman umum: gagasan bahwa shell sedang melakukan sesuatu untuk memfasilitasi interaksi antara proses anaknya dan terminal. Itu tidak benar. Setelah melakukan pekerjaan pengaturan (proses pembuatan, pengaturan mode terminal, pembuatan pipa dan pengalihan file deskriptor lainnya, dan menjalankan program target) shell hanya menunggu . Apa yang Anda ketik
cat
tidak akan melalui shell, apakah itu input normal atau karakter khusus penghasil sinyal^C
. Thecat
proses memiliki akses langsung ke terminal melalui deskriptor file sendiri, dan terminal memiliki kemampuan untuk mengirim sinyal langsung kecat
proses karena kelompok proses latar depan.Setelah
cat
proses mati, shell akan diberi tahu, karena itu adalah induk daricat
proses. Kemudian shell menjadi aktif dan menempatkan dirinya di latar depan lagi.Inilah latihan untuk meningkatkan pemahaman Anda.
Pada prompt shell di terminal baru, jalankan perintah ini:
Kata
exec
kunci menyebabkan shell dieksekusicat
tanpa membuat proses anak. Shell diganti olehcat
. PID yang sebelumnya milik shell sekarang menjadi PID daricat
. Verifikasi ini denganps
di terminal yang berbeda. Ketik beberapa baris acak dan lihat yangcat
mengulanginya kembali kepada Anda, membuktikan bahwa itu masih berperilaku normal meskipun tidak memiliki proses shell sebagai orangtua. Apa yang akan terjadi ketika Anda menekan^C
sekarang?Menjawab:
sumber
exec cat
menekan^C
tidak hanya akan^C
menjadi kucing. Mengapa itu mengakhiricat
yang sekarang telah menggantikan shell? Karena shell telah diganti, shell adalah hal yang mengimplementasikan logika mengirim SIGINT kepada anak-anaknya setelah menerima^C
.Tidak ada alasan untuk menyebarkan
SIGINT
ke anak. Selain itusystem()
spesifikasi POSIX mengatakan: "Fungsi sistem () harus mengabaikan sinyal SIGINT dan SIGQUIT, dan akan memblokir sinyal SIGCHLD, sambil menunggu perintah untuk berakhir."Jika shell menyebarkan diterima
SIGINT
, misalnya mengikuti Ctrl-C nyata, ini berarti bahwa proses anak akan menerimaSIGINT
sinyal dua kali, yang mungkin memiliki perilaku yang tidak diinginkan.sumber
system()
. Tapi Anda benar, jika itu menangkap sinyal (jelas itu benar) maka tidak ada alasan untuk menyebarkannya ke bawah.setpgid
Contoh minimal grup proses POSIX CMungkin lebih mudah untuk memahami dengan contoh API yang bisa dijalankan yang minimal bisa dijalankan.
Ini menggambarkan bagaimana sinyal dikirim ke anak, jika anak tidak mengubah grup prosesnya
setpgid
.main.c
GitHub hulu .
Kompilasi dengan:
Jalankan tanpa
setpgid
Tanpa argumen CLI,
setpgid
tidak akan dilakukan:Hasil yang mungkin:
dan programnya hang.
Seperti yang bisa kita lihat, pgid dari kedua proses itu sama, seperti yang diwarisi
fork
.Lalu setiap kali Anda menekan:
Output lagi:
Ini menunjukkan bagaimana:
kill(-pgid, SIGINT)
Tutup program dengan mengirimkan sinyal berbeda ke kedua proses, misalnya SIGQUIT dengan
Ctrl + \
.Jalankan dengan
setpgid
Jika Anda menjalankan argumen, misalnya:
kemudian anak mengubah pgid-nya, dan sekarang hanya satu sigint yang dicetak setiap waktu dari induknya saja:
Dan sekarang, setiap kali Anda menekan:
hanya orang tua yang menerima sinyal juga:
Anda masih dapat membunuh orang tua seperti sebelumnya dengan SIGQUIT:
namun anak sekarang memiliki PGID yang berbeda, dan tidak menerima sinyal itu! Ini bisa dilihat dari:
Anda harus membunuhnya secara eksplisit dengan:
Ini memperjelas mengapa grup sinyal ada: jika tidak kita akan mendapatkan banyak proses yang tersisa untuk dibersihkan secara manual setiap saat.
Diuji pada Ubuntu 18.04.
sumber