Kontrol proses mana yang dibatalkan oleh Ctrl + C

13

Saya memiliki CD live yang menjalankan boot ke Linux dan menjalankan skrip Bash kecil. Script mencari dan menjalankan program kedua (yang biasanya merupakan biner C ++ yang dikompilasi).

Anda seharusnya dapat membatalkan program kedua dengan menekan Ctrl+ C. Apa yang harus terjadi adalah bahwa program kedua berhenti, dan skrip Bash terus menjalankan pembersihan. Apa yang sebenarnya terjadi adalah bahwa aplikasi utama dan skrip Bash berakhir. Yang merupakan masalah.

Jadi saya menggunakan trapbuiltin untuk memberitahu Bash untuk mengabaikan SIGINT. Dan sekarang Ctrl+ Cmenghentikan aplikasi C ++, tetapi Bash terus menjalankannya. Bagus.

Oh ya ... Terkadang "aplikasi kedua" adalah skrip Bash yang lain. Dan dalam yang terjadi, Ctrl+ Csekarang tidak apa-apa pun .

Jelas pemahaman saya tentang cara kerja barang ini salah ... Bagaimana cara saya mengontrol proses mana yang mendapatkan SIGINT ketika pengguna menekan Ctrl+ C? Saya ingin mengarahkan sinyal ini ke satu proses tertentu saja .

Matematika Matematika
sumber

Jawaban:

11

Setelah berjam-jam mencari wajah Internet, saya telah menemukan jawabannya.

  1. Linux memiliki gagasan tentang grup proses .

  2. Driver TTY memiliki gagasan Foreground Process Group.

  3. Ketika Anda menekan Ctrl+ C, TTY mengirim SIGINTke setiap proses di Grup Proses Foreground. (Lihat juga entri blog ini .)

Inilah sebabnya mengapa biner yang dikompilasi dan skrip yang meluncurkan keduanya menjadi musnah. Sebenarnya saya hanya ingin aplikasi utama menerima sinyal ini, bukan skrip startup.

Solusinya sekarang jelas: Kita perlu menempatkan aplikasi dalam grup proses baru, dan menjadikannya Grup Proses Foreground untuk TTY ini. Rupanya perintah untuk melakukan itu adalah

setsid -c <applcation>

Dan itu saja. Sekarang ketika pengguna menekan Ctrl+ C, SIGINT akan dikirim ke aplikasi (dan semua anak yang dimilikinya) dan tidak ada orang lain. Itulah yang saya inginkan.

  • setsid dengan sendirinya menempatkan aplikasi dalam grup proses baru (memang, seluruh "sesi" baru, yang tampaknya merupakan grup grup proses).

  • Menambahkan -cbendera menjadikan grup proses baru ini menjadi grup proses "foreground" untuk TTY saat ini. (Yaitu, mendapat SIGINTketika Anda menekan Ctrl+ C)

Saya telah melihat banyak informasi yang saling bertentangan tentang kapan Bash menjalankan atau tidak menjalankan proses dalam grup proses baru. (Secara khusus, tampaknya berbeda untuk kerang "interaktif" dan "non-interaktif"). Saya telah melihat saran bahwa Anda mungkin bisa membuat ini bekerja dengan tipuan pipa pintar ... Saya tidak tahu. Tetapi pendekatan di atas tampaknya bekerja untuk saya.

Matematika Matematika
sumber
5
Anda hampir mendapatkannya ... kontrol pekerjaan dinonaktifkan secara default saat menjalankan skrip, tetapi Anda dapat mengaktifkannya dengan set -m. Ini sedikit lebih bersih dan sederhana daripada menggunakan setsidsetiap kali Anda menjalankan anak.
psusi
@psusi Terima kasih atas tipnya! Saya hanya perlu menjalankan satu anak, jadi bukan masalah besar. Saya sekarang tahu di mana mencarinya di manual Bash ...
MathematicalOrchid
Ironisnya, saya memiliki masalah sebaliknya di mana saya ingin orang tua menangkap sigint, tetapi itu bukan karena "tipu daya pipa yang pintar." Juga maju grup -> grup proses
Andrew Domaszek
2

Seperti yang saya sebutkan di komentar untuk f01, Anda harus mengirim SIGTERM ke proses anak. Berikut adalah beberapa skrip yang menunjukkan cara menjebak ^ C dan mengirim sinyal ke proses anak.

Pertama, orang tua.

tersentuh

#!/bin/bash

# trap test
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
child=sleeploop

set_trap()
{
    sig=$1
    msg="echo -e \"\n$myname received ^C, sending $sig to $child, $pid\""
    trap "$msg; kill -s $sig $pid" SIGINT
}
trap "echo \"bye from $myname\"" EXIT

echo "running $child..."
./$child 5  &
pid=$!

# set_trap SIGINT
set_trap SIGTERM
echo "$child pid = $pid"

wait $pid
echo "$myname finished waiting"

Dan sekarang, anak itu.

sleeploop

#!/bin/bash

# child script for traptest
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
delay="$1"

set_trap()
{
    sig=$1
    trap "echo -e '\n$myname received $sig signal';exit 0" $sig
}

trap "echo \"bye from $myname\"" EXIT
set_trap SIGTERM
set_trap SIGINT

#Select sleep mode
if false
then
    echo "Using foreground sleep"
    Sleep()
    {
        sleep $delay
    }
else
    echo "Using background sleep"
    Sleep()
    {
        sleep "$delay" &
        wait $!
    }
fi

#Time to snooze :)
for ((i=0; i<5; i++));
do
    echo "$i: sleeping for $delay"
    Sleep
done

echo "$myname terminated normally"

Jika traptest mengirim SIGTERM hal-hal berperilaku baik, tetapi jika traptest mengirim SIGINT maka sleeploop tidak pernah melihatnya.

Jika sleeploop menjebak SIGTERM, dan sleep mode adalah foreground, maka sleeploop tidak menangkap sinyal sampai bangun dari sleep yang sekarang. Tetapi jika mode tidur adalah latar belakang, itu akan segera merespons.

PM 2Ring
sumber
Terima kasih atas contoh luar biasa ini, ini banyak membantu saya dalam memahami bagaimana saya dapat meningkatkan skrip saya :)
TabeaKischka
2

Dalam skrip bash awal Anda.

  • melacak PID dari program kedua

  • menangkap SIGINT

  • ketika Anda telah menangkap SIGINT, kirim SIGINT ke PID program kedua

f01
sumber
1
Itu mungkin tidak membantu. Jika Anda menjebak SIGINT pada skrip induk maka skrip anak tidak akan dapat menerima SIGINT, baik Anda mengirimnya dari induknya atau dari shell lain. Tapi itu tidak seburuk kedengarannya, karena Anda dapat mengirim SIGTERM ke anak, yang tampaknya merupakan sinyal yang disukai untuk digunakan.
PM 2Ring
1
Mengapa SIGTERM "lebih disukai"?
MathematicalOrchid
Mereka hampir mirip. SIGINT adalah sinyal yang dikirim oleh terminal / pengguna pengendali mis. Ctrl + C. SIGTERM adalah apa yang juga dapat Anda kirim jika Anda ingin prosesnya dihentikan. Lebih banyak pemikiran di sini en.wikipedia.org/wiki/Unix_signal
f01