SIGINT, üst sürecine gönderildiğinde neden çocuk sürecine yayılmıyor?


62

Bir kabuk işlemi (örn. sh) Ve alt işlemi (örn. cat) Göz önüne alındığında , kabuğun işlem kimliğini kullanarak Ctrl+ davranışını nasıl simüle edebilirim C?


Ben denedim budur:

Çalışan shve sonra cat:

[user@host ~]$ sh
sh-4.3$ cat
test
test

Gönderme SIGINTiçin catbaşka bir terminalden:

[user@host ~]$ kill -SIGINT $PID_OF_CAT

cat sinyali aldı ve sonlandırıldı (beklendiği gibi).

Sinyali üst işleme göndermek, işe yaramaz gibi görünmüyor. Sinyal cat, ana işlemine gönderildiğinde neden yayılmıyor sh?

Bu çalışmıyor:

[user@host ~]$ kill -SIGINT $PID_OF_SH

1
Kabuğun klavyeden veya terminalden gönderilmeyen SIGINT sinyallerini görmezden gelme yolu vardır.
konsolebox

Yanıtlar:


86

CTRL+ Nasıl Cçalışır

İlk şey CTRL+ nasıl Cçalıştığını anlamaktır .

CTRL+ CDüğmesine bastığınızda , terminal emülatörünüz bir ETX karakteri (metin sonu / 0x03) gönderir.
TTY, bu karakteri aldığında, terminalin ön plan işlem grubuna bir SIGINT gönderecek şekilde yapılandırılmıştır. Bu yapılandırma yaparak sttyve bakarak görülebilir intr = ^C;. POSIX şartname INTR alındığında, söz konusu terminalin önalan süreç grubuna bir SIGINT göndermek gerektiğini söylüyor.

Ön plan süreç grubu nedir?

Öyleyse, şimdi soru şu ki, ön plan gruplarının ne olduğunu nasıl belirlersiniz? Ön plan işlem grubu, sadece klavye tarafından üretilen sinyalleri alacak olan işlem grubudur (SIGTSTOP, SIGINT, vb.).

İşlem grubu kimliğini belirlemenin en basit yolu kullanmaktır ps:

ps ax -O tpgid

İkinci sütun, işlem grubu kimliği olacaktır.

İşlem grubuna nasıl sinyal gönderirim?

Şimdi işlem grubu kimliğinin ne olduğunu bildiğimize göre, tüm gruba bir sinyal gönderme POSIX davranışını simüle etmemiz gerekiyor.

Bu , grup kimliği önüne killkoyarak yapılabilir -.
Örneğin, işlem grubu kimliğiniz 1234 ise, şunları kullanırsınız:

kill -INT -1234

 


Terminal numarasını kullanarak CTRL+ simülasyonunu yapın C.

Yukarıdakiler CTRL+ ' Cnın manuel bir süreç olarak nasıl simüle edileceğini kapsar . Peki ya TTY numarasını biliyorsanız ve bu terminal için CTRL+ simülasyonunu yapmak istiyorsanız C?

Bu çok kolay olur.

Varsayalım $ttyki hedeflemek istediğiniz terminaldir (terminalde çalışarak bunu elde edebilirsiniz tty | sed 's#^/dev/##').

kill -INT -$(ps h -t $tty -o tpgid | uniq)

Bu, ön plan işlem grubunun ne olduğuna göre bir SIGINT gönderir $tty.


6
Doğrudan terminal bypass izin kontrolünden gelen sinyallerin kontrol edilmesine dikkat etmek önemlidir; bu nedenle, Ctrl + C, terminal niteliklerinde kapatmazsanız sinyalleri iletmede başarılı olur, oysa bir killkomut başarısız olabilir.
Brian Bi

4
+1, içinsends a SIGINT to the foreground process group of the terminal.
andy

Çocuğun süreç grubunun sonraki ebeveyn ile aynı olduğunu söylemeye değer fork. Minimal çalıştırılabilir C örneği, şu adrestedir
Ciro Santilli

15

Vinc17'nin dediği gibi, bunun olması için bir sebep yok. Bir sinyal oluşturucu anahtar dizilimi (örneğin, Ctrl+ C) yazdığınızda, sinyal terminale bağlı (ilişkili) tüm işlemlere gönderilir. Tarafından üretilen sinyaller için böyle bir mekanizma yoktur kill.

Ancak, bir komut gibi

kill -SIGINT -12345

12345 işlem grubundaki tüm işlemlere sinyal gönderir ; bkz (1) öldürmek ve (2) öldürmek . Bir kabuğun çocukları tipik olarak kabuğun işlem grubundadır (en azından eşzamansız değilse), bu nedenle sinyali kabuğun PID'sinin negatifine göndermek istediğiniz şeyi yapabilir.


Hata

Vinc17'nin işaret ettiği gibi, bu etkileşimli mermiler için işe yaramaz. İşte bir alternatiftir olabilir çalışır:

kill -SIGINT - $ (eko $ (ps -p PID_of_shell o tpgid =))

ps -pPID_of_shellKabuk hakkında işlem bilgisi alır.  o tpgid=başlıksız ps, sadece terminal işlem grubu kimliğini çıkarmasını söyler . Bu 10000'den az ise, psonu baştaki boşluklarla gösterecektir; $(echo …)boşluk ön (ve arka) kapalı şerit hızlı numara.

Bunu bir Debian makinesinde lanetli testlerde çalışmasını sağladım.


1
Bu işlem etkileşimli bir kabukta (OP'nin kullandığı şey) başlatıldığında işe yaramaz. Yine de bu davranış için bir referansım yok.
vinc17

12

Soru kendi cevabını içerir. Gönderme SIGINTiçin catsürecin killtuşuna ne olur mükemmel bir simülasyon olduğunu ^C.

Daha kesin olmak gerekirse, kesme karakteri ( ^Cvarsayılan olarak) SIGINTterminalin ön plan işlem grubundaki her işleme gönderilir . Bunun yerine catbirden fazla işlemi içeren daha karmaşık bir komut çalıştırıyorsanız, aynı etkiyi elde etmek için proses grubunu öldürmeniz gerekir ^C.

Herhangi bir harici komutu &arka plan işleci olmadan çalıştırdığınızda , kabuk komut için yeni bir işlem grubu oluşturur ve bu işlem grubunun şimdi ön planda olduğunu terminale bildirir. Kabuk hala ön planda olmayan kendi süreç grubunda. Ardından kabuk komutun çıkmasını bekler.

Ortak bir yanılgıdan mağdur olmuş göründüğünüz yer burasıdır: kabuğun, çocuk süreci ile terminal arasındaki etkileşimi kolaylaştıracak bir şey yaptığı fikri. Bu sadece doğru değil. Kurulum işlemini tamamladıktan sonra (işlem oluşturma, terminal modu ayarı, boruların oluşturulması ve diğer dosya tanımlayıcılarının yönlendirilmesi ve hedef programın yürütülmesi) kabuk bekler . Yazdığınız catşey kabuktan geçmiyor, normal giriş veya sinyal üreten özel bir karakter olsun ^C. catSüreç kendi dosya tanımlayıcıları yoluyla terminale doğrudan erişimi vardır ve terminal doğrudan sinyalleri göndermek yeteneğine sahiptir cato öncelikli süreç grubu çünkü süreç.

catİşlem öldükten sonra, kabuğa haber verilecektir, çünkü catişlemin ebeveynidir . Sonra kabuk aktif hale gelir ve kendini tekrar ön plana çıkarır.

Anlayışınızı artırmak için bir alıştırma.

Yeni bir terminaldeki kabuk isteminde şu komutu çalıştırın:

exec cat

execAnahtar kelime yürütmek için kabuk neden catbir çocuk süreç yaratmadan. Kabuk tarafından değiştirilir cat. Eskiden kabuğa ait olan PID şimdi de PID'dir cat. Bunu psfarklı bir terminalde doğrulayın . Bazı rasgele satırlar yazın ve catbunları size tekrar tekrar ettiğini görün; bunun bir ebeveyn olarak bir kabuk işlemine sahip olmamasına rağmen hala normal davrandığını kanıtlayın. Şimdi basarsan ne olacak ^C?

Cevap:

SIGINT, ölen kedi işlemine iletilir. Terminaldeki tek işlem olduğu için oturum, tıpkı bir kabuk isteminde "çıkış" demiştin gibi bitiyor. Aslında kedi oldu Bir süre kabuğu.


Kabuk yoldan çekildi. +1
Piotr Dobrogost

Neden exec catbaskı yaptıktan sonra ^Csadece ^Ckedinin içine inmeyeceğini anlamıyorum . Neden catşimdi kabuğun yerini alanını sonlandırdı ? Kabuk değiştirildiğinden, kabuk SIGINT'i aldıktan sonra çocuklarına gönderme mantığını uygulayan şeydir ^C.
Steven Lu,

Nokta kabuk olmasıdır gelmez onun çocuklarına SIGINT göndermek. SIGINT terminal sürücüsünden gelir ve tüm ön plan işlemlerine gönderilir.

3

SIGINTÇocuğu yaymak için hiçbir sebep yok . Ayrıca system()POSIX şartnamesi şöyle diyor: "system () işlevi, SIGINT ve SIGQUIT sinyallerini görmezden gelmeli ve komutun sonlandırılmasını beklerken SIGCHLD sinyalini engellemelidir."

Kabuk SIGINT, örneğin gerçek bir Ctrl-C'yi takiben alınan iletiyi çoğalttıysa, bu, alt işlemin SIGINT, istenmeyen davranışa sahip olabilecek sinyali iki kez alacağı anlamına gelir .


Kabuğun bunu uygulamak zorunda olması gerekmez system(). Ama haklısın, eğer sinyali yakalarsa (belli ki öyle yapar) o zaman aşağı doğru yaymak için hiçbir sebep yoktur.
goldilocks

@goldilocks Cevabımı tamamladım, belki daha iyi bir sebep göstererek. Kabuğun çocuğun sinyali zaten alıp almadığını bilmediğine dikkat edin, bu nedenle sorun.
vinc17

1

setpgid POSIX C işlem grubu minimal örneği

Temel API'nin en düşük çalıştırılabilir örneğini anlamak daha kolay olabilir.

Bu, çocuğun kendi süreç grubunu değiştirmemesi durumunda, sinyalin çocuğa nasıl gönderildiğini gösterir setpgid.

main.c

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

volatile sig_atomic_t is_child = 0;

void signal_handler(int sig) {
    char parent_str[] = "sigint parent\n";
    char child_str[] = "sigint child\n";
    signal(sig, signal_handler);
    if (sig == SIGINT) {
        if (is_child) {
            write(STDOUT_FILENO, child_str, sizeof(child_str) - 1);
        } else {
            write(STDOUT_FILENO, parent_str, sizeof(parent_str) - 1);
        }
    }
}

int main(int argc, char **argv) {
    pid_t pid, pgid;

    (void)argv;
    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    pid = fork();
    assert(pid != -1);
    if (pid == 0) {
        is_child = 1;
        if (argc > 1) {
            /* Change the pgid.
             * The new one is guaranteed to be different than the previous, which was equal to the parent's,
             * because `man setpgid` says:
             * > the child has its own unique process ID, and this PID does not match
             * > the ID of any existing process group (setpgid(2)) or session.
             */
            setpgid(0, 0);
        }
        printf("child pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)getpgid(0));
        assert(kill(getppid(), SIGUSR1) == 0);
        while (1);
        exit(EXIT_SUCCESS);
    }
    /* Wait until the child sends a SIGUSR1. */
    pause();
    pgid = getpgid(0);
    printf("parent pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)pgid);
    /* man kill explains that negative first argument means to send a signal to a process group. */
    kill(-pgid, SIGINT);
    while (1);
}

GitHub yukarı akış .

İle derleyin:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -Wpedantic -o setpgid setpgid.c

Olmadan koş setpgid

Herhangi bir CLI argümanı olmadan, setpgidyapılmaz:

./setpgid

Muhtemel sonuç:

child pid, pgid = 28250, 28249
parent pid, pgid = 28249, 28249
sigint parent
sigint child

ve program kilitleniyor.

Gördüğümüz gibi, her iki sürecin dehşeti, karşısında kaldığı gibi aynı fork.

Sonra ne zaman vurursanız olun:

Ctrl + C

Yine çıktılar:

sigint parent
sigint child

Bu nasıl gösterir:

  • Tüm işlem grubuna bir sinyal göndermek için kill(-pgid, SIGINT)
  • Terminaldeki Ctrl + C, tüm işlem grubuna varsayılan olarak bir kill gönderir

Her iki işleme farklı bir sinyal göndererek programdan çıkın, örneğin SIGQUIT with Ctrl + \.

İle koş setpgid

Bir argümanla koşarsanız, örneğin:

./setpgid 1

Daha sonra çocuk kendi pgidini değiştirir ve şimdi yalnızca ebeveynlerden her seferinde yalnızca bir tek imza basılır:

child pid, pgid = 16470, 16470
parent pid, pgid = 16469, 16469
sigint parent

Ve şimdi, ne zaman vurursanız:

Ctrl + C

sadece ebeveynler de sinyali alır:

sigint parent

Bir SIGQUIT ile ebeveyni daha önce olduğu gibi öldürebilirsin:

Ctrl + \

ancak çocuğun artık farklı bir PGID'si var ve bu sinyali almıyor! Bu görülebilir:

ps aux | grep setpgid

Şunlarla açıkça öldürmeniz gerekecek:

kill -9 16470

Bu, sinyal gruplarının neden var olduğunu açıkça ortaya koyuyor: aksi takdirde, her zaman manuel olarak temizlenecek bir sürü işlem bırakmış oluruz.

Ubuntu 18.04'te test edilmiştir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.