अपनी मूल प्रक्रिया में भेजे जाने पर SIGINT को बाल प्रक्रिया का प्रचार क्यों नहीं किया जाता है?


62

शेल प्रक्रिया (जैसे sh) और इसकी बाल प्रक्रिया (जैसे cat) को देखते हुए , मैं शेल की प्रक्रिया आईडी का उपयोग करके Ctrl+ के व्यवहार का अनुकरण कैसे कर सकता हूं C?


यही मैंने कोशिश की है:

चल रहा है shऔर फिर cat:

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

भेजा जा रहा है SIGINTकरने के लिए catएक और टर्मिनल से:

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

cat संकेत प्राप्त किया और समाप्त कर दिया (उम्मीद के मुताबिक)।

मूल प्रक्रिया के लिए संकेत भेजना काम नहीं करता है। जब इसकी मूल प्रक्रिया में भेजा जाता है तो संकेत का प्रचार क्यों नहीं किया जाता catहै sh?

यह काम नहीं करता:

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

1
शेल में कीबोर्ड या टर्मिनल से भेजे गए SIGINT संकेतों को अनदेखा करने का एक तरीका है।
konsolebox

जवाबों:


86

कैसे CTRL+ Cकाम करता है

पहली बात यह समझना है कि कैसे CTRL+ Cकाम करता है।

जब आप CTRL+ दबाते हैं C, तो आपका टर्मिनल एमुलेटर ETX कैरेक्टर (एंड-ऑफ-टेक्स्ट / 0x03) भेजता है।
TTY को इस तरह कॉन्फ़िगर किया गया है कि जब वह इस वर्ण को प्राप्त करता है, तो यह टर्मिनल के अग्रभूमि प्रक्रिया समूह में एक संकेत भेजता है। इस विन्यास को देखने sttyऔर देखने के द्वारा किया जा सकता है intr = ^C;POSIX विनिर्देशन का कहना है कि जब INTR प्राप्त होता है, यह है कि टर्मिनल के अग्रभूमि प्रक्रिया समूह के लिए एक SIGINT भेजना चाहिए।

अग्रभूमि प्रक्रिया समूह क्या है?

तो, अब सवाल यह है कि आप कैसे निर्धारित करते हैं कि अग्रभूमि प्रक्रिया समूह क्या है? अग्रभूमि प्रक्रिया समूह बस प्रक्रियाओं का समूह है जो कीबोर्ड द्वारा उत्पन्न कोई भी संकेत प्राप्त करेगा (SIGTSTOP, SIGINT, आदि)।

प्रक्रिया समूह ID का उपयोग करने के लिए निर्धारित करने का सबसे सरल तरीका है ps:

ps ax -O tpgid

दूसरा कॉलम प्रक्रिया समूह आईडी होगा।

मैं प्रक्रिया समूह को एक संकेत कैसे भेजूं?

अब जब हम जानते हैं कि प्रक्रिया समूह आईडी क्या है, तो हमें पूरे समूह को एक संकेत भेजने के POSIX व्यवहार का अनुकरण करने की आवश्यकता है।

यह ग्रुप आईडी के सामने killलगाकर किया जा सकता है -
उदाहरण के लिए, यदि आपकी प्रक्रिया समूह आईडी 1234 है, तो आप उपयोग करेंगे:

kill -INT -1234

 


टर्मिनल नंबर का उपयोग करके CTRL+ अनुकरण करें C

इसलिए उपरोक्त एक मैनुअल प्रक्रिया के रूप में CTRL+ अनुकरण कैसे करें को कवर करता Cहै। लेकिन क्या होगा यदि आप TTY नंबर जानते हैं, और आप उस टर्मिनल के लिए CTRL+ अनुकरण करना चाहते हैं C?

यह बहुत आसान हो जाता है।

मान लें $ttyकि आप जिस टर्मिनल को लक्षित करना चाहते हैं (आप इसे tty | sed 's#^/dev/##'टर्मिनल में चलाकर प्राप्त कर सकते हैं )।

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

यह जो भी अग्रभूमि प्रक्रिया समूह है, उसे एक संकेत भेजेगा $tty


6
यह इंगित करने के लायक है कि सिग्नल जो सीधे टर्मिनल बाईपास अनुमति जाँच से आते हैं, इसलिए Ctrl + C सिग्नल देने में हमेशा सफल होता है जब तक कि आप इसे टर्मिनल विशेषताओं में बंद नहीं करते, जबकि एक killकमांड विफल हो सकती है।
ब्रायन

4
+1,sends a SIGINT to the foreground process group of the terminal.
andy

यह उल्लेख करते हुए कि बच्चे का प्रक्रिया समूह माता-पिता के बाद जैसा है fork। न्यूनतम रननीय
Ciro Santilli 新疆

15

जैसा कि vinc17 कहता है, ऐसा होने का कोई कारण नहीं है। जब आप एक सिग्नल-जनरेटिंग कुंजी अनुक्रम (जैसे, Ctrl+ C) टाइप करते हैं , तो सिग्नल को उन सभी प्रक्रियाओं को भेजा जाता है जो टर्मिनल से जुड़ी (जुड़ी हुई) हैं। द्वारा उत्पन्न संकेतों के लिए ऐसा कोई तंत्र नहीं है kill

हालाँकि, एक कमांड की तरह

kill -SIGINT -12345

प्रक्रिया समूह 12345 में सभी प्रक्रियाओं को संकेत भेजेगा ; देखें मारने (1) और मारने (2) । शेल के बच्चे आम तौर पर शेल के प्रक्रिया समूह में होते हैं (कम से कम, यदि वे अतुल्यकालिक नहीं हैं), इसलिए शेल के पीआईडी ​​के नकारात्मक को संकेत भेजना वह कर सकता है जो आप चाहते हैं।


उफ़

जैसा कि vinc17 बताता है, यह इंटरैक्टिव गोले के लिए काम नहीं करता है। यहाँ एक विकल्प है जो काम कर सकता है :

किल-सिगंट - $ (गूंज $ (ps -p PID_of_shell o tpgid =))

ps -pPID_of_shellशेल पर प्रक्रिया की जानकारी मिलती है।  बिना किसी हेडर के केवल टर्मिनल प्रोसेस ग्रुप आईडी को आउटपुट करने के लिए o tpgid=कहता psहै। यदि यह 10000 से कम है, psतो इसे प्रमुख स्थान (नों) के साथ प्रदर्शित करेगा; $(echo …)एक त्वरित चाल प्रमुख (और अनुगामी) रिक्त स्थान बंद पट्टी है।

मुझे यह डेबियन मशीन पर सरसरी परीक्षण में काम करने के लिए मिला।


1
यह तब काम नहीं करता है जब प्रक्रिया एक इंटरैक्टिव शेल में शुरू की जाती है (जो कि ओपी उपयोग कर रहा है)। मेरे पास इस व्यवहार के लिए कोई संदर्भ नहीं है, हालांकि।
vinc17

12

प्रश्न का अपना उत्तर होता है। भेजा जा रहा है SIGINTकरने के लिए catके साथ प्रक्रिया killक्या होता है जब आप प्रेस का एक आदर्श अनुकरण है ^C

अधिक सटीक होने के लिए, टर्मिनल के अग्रभूमि प्रक्रिया समूह में व्यवधान वर्ण ( ^Cडिफ़ॉल्ट रूप से) SIGINTहर प्रक्रिया को भेजता है। यदि आप के बजाय catकई प्रक्रियाओं से अधिक जटिल आदेश चला रहे थे, तो आपको उसी प्रभाव को प्राप्त करने के लिए प्रक्रिया समूह को मारना होगा ^C

जब आप &पृष्ठभूमि ऑपरेटर के बिना किसी भी बाहरी कमांड को चलाते हैं , तो शेल कमांड के लिए एक नया प्रोसेस ग्रुप बनाता है और टर्मिनल को सूचित करता है कि यह प्रोसेस ग्रुप अब अग्रभूमि में है। शेल अभी भी अपने स्वयं के प्रक्रिया समूह में है, जो अब अग्रभूमि में नहीं है। फिर शेल कमांड से बाहर निकलने का इंतजार करता है।

ऐसा लगता है कि आप एक आम ग़लतफ़हमी के शिकार हो गए हैं: यह विचार कि शेल अपने बच्चे की प्रक्रिया (तों) और टर्मिनल के बीच बातचीत को सुविधाजनक बनाने के लिए कुछ कर रहा है। यह सिर्फ सच नहीं है। एक बार यह सेटअप कार्य (प्रक्रिया निर्माण, टर्मिनल मोड सेटिंग, पाइपों का निर्माण और अन्य फ़ाइल विवरणकों के पुनर्निर्देशन, और लक्ष्य कार्यक्रम को निष्पादित करने) के बाद शेल केवल इंतजार करता है । आप जो टाइप करते हैं catवह शेल से नहीं गुजर रहा है, चाहे वह सामान्य इनपुट हो या सिग्नल-जनरेट करने वाला विशेष कैरेक्टर ^C। इस catप्रक्रिया की अपनी फ़ाइल डिस्क्रिप्टर के माध्यम से टर्मिनल तक सीधी पहुंच है, और टर्मिनल में catप्रक्रिया को सीधे सिग्नल भेजने की क्षमता है क्योंकि यह अग्रभूमि प्रक्रिया समूह है।

catप्रक्रिया के मरने के बाद , शेल को अधिसूचित किया जाएगा, क्योंकि यह catप्रक्रिया का जनक है। फिर खोल सक्रिय हो जाता है और फिर से अग्रभूमि में डाल देता है।

यहाँ आपकी समझ बढ़ाने के लिए एक अभ्यास है।

नए टर्मिनल में शेल प्रॉम्प्ट पर, यह कमांड चलाएँ:

exec cat

execकीवर्ड खोल निष्पादित करने के लिए कारण बनता है catएक बच्चे की प्रक्रिया बनाने के बिना। खोल द्वारा प्रतिस्थापित किया जाता है cat। PID जो पहले शेल से संबंधित था, अब PID है cat। इसे psएक अलग टर्मिनल में सत्यापित करें । कुछ रैंडम लाइनें टाइप करें और देखें कि यह catउन्हें आपके पास दोहराता है, यह साबित करता है कि माता-पिता के रूप में शेल प्रक्रिया नहीं होने के बावजूद यह सामान्य रूप से व्यवहार कर रहा है। जब आप दबाएंगे तब क्या होगा ^C?

उत्तर:

SIGINT को बिल्ली प्रक्रिया में वितरित किया जाता है, जो मर जाता है। क्योंकि यह टर्मिनल पर एकमात्र प्रक्रिया थी, सत्र समाप्त होता है, जैसे कि आपने शेल प्रॉम्प्ट पर "निकास" कहा था। वास्तव में बिल्ली कुछ समय के लिए आपका खोल थी


खोल रास्ते से बाहर हो गया है। +1
पिओटर डोब्रोगोस्ट

मुझे समझ में नहीं आता कि exec catदबाने के बाद ^Cसिर्फ ^Cबिल्ली में क्यों नहीं उतरना चाहिए। यह क्यों समाप्त हो जाएगा catजो अब खोल को बदल दिया है? चूंकि शेल को बदल दिया गया है, शेल वह चीज है जो प्राप्त होने पर अपने बच्चों को SIGINT भेजने के तर्क को लागू करता है ^C
स्टीवन लू

मुद्दा यह है कि यह शेल अपने बच्चों को SIGINT नहीं भेजता है। SIGINT टर्मिनल ड्राइवर से आता है, और सभी अग्रभूमि प्रक्रियाओं में भेजा जाता है।

3

SIGINTबच्चे को प्रचार करने का कोई कारण नहीं है । इसके अलावा system()POSIX विनिर्देश कहता है: "सिस्टम () फ़ंक्शन SIGINT और SIGQUIT संकेतों को अनदेखा करेगा, और SIGCHLD सिग्नल को ब्लॉक करेगा, जबकि कमांड समाप्त होने का इंतजार कर रहा है।"

यदि शेल ने प्राप्त प्रचारित किया SIGINT, उदाहरण के लिए एक वास्तविक Ctrl-C का अनुसरण करते हुए , इसका मतलब यह होगा कि बच्चे की प्रक्रिया को SIGINTदो बार सिग्नल प्राप्त होगा , जिसमें अवांछित व्यवहार हो सकता है।


शेल को इसे लागू नहीं करना है system()। लेकिन आप सही हैं, अगर यह सिग्नल पकड़ता है (जाहिर है यह करता है) तो इसे नीचे की ओर प्रचारित करने का कोई कारण नहीं है।
गोल्डीलॉक्स

@goldilocks मैंने अपना जवाब पूरा कर लिया है, शायद बेहतर कारण दे रहा हूँ। ध्यान दें कि शेल को यह पता नहीं चल सकता है कि बच्चे को पहले ही संकेत मिल चुका है, इसलिए समस्या।
vinc17

1

setpgid POSIX C प्रक्रिया समूह न्यूनतम उदाहरण

अंतर्निहित एपीआई के न्यूनतम रननीय उदाहरण के साथ समझना आसान हो सकता है।

यह दिखाता है कि बच्चे को सिग्नल कैसे भेजा जाता है, अगर बच्चा अपने प्रक्रिया समूह को नहीं बदलता है 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);
}

गिटहब ऊपर

संकलन:

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

बिना भागो setpgid

बिना किसी CLI तर्क के, setpgidनहीं किया जाता है:

./setpgid

संभावित परिणाम:

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

और कार्यक्रम लटक गया।

जैसा कि हम देख सकते हैं, दोनों प्रक्रियाओं का pgid एक जैसा है, क्योंकि यह विरासत में मिला है fork

फिर जब भी आप मारा:

Ctrl + C

यह फिर से आउटपुट करता है:

sigint parent
sigint child

यह दिखाता है कि:

  • के साथ एक संपूर्ण प्रक्रिया समूह को संकेत भेजने के लिए kill(-pgid, SIGINT)
  • टर्मिनल पर Ctrl + C डिफ़ॉल्ट रूप से संपूर्ण प्रक्रिया समूह को एक किल भेजता है

दोनों प्रक्रियाओं, जैसे SIGQUIT के साथ एक अलग संकेत भेजकर कार्यक्रम से बाहर निकलें Ctrl + \

साथ दौड़ो setpgid

यदि आप तर्क के साथ चलते हैं, जैसे:

./setpgid 1

तब बच्चा अपना pgid बदलता है, और अब केवल माता-पिता से हर बार केवल एक ही हस्ताक्षर छपता है:

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

और अब, जब भी आप मारा:

Ctrl + C

केवल अभिभावक ही संकेत प्राप्त करता है:

sigint parent

आप अभी भी माता-पिता को पहले की तरह मार सकते हैं:

Ctrl + \

हालाँकि बच्चे के पास अब एक अलग पीजीआईडी ​​है, और वह संकेत प्राप्त नहीं करता है! इससे देखा जा सकता है:

ps aux | grep setpgid

आपको इसे स्पष्ट रूप से मारना होगा:

kill -9 16470

इससे यह स्पष्ट हो जाता है कि सिग्नल समूह क्यों मौजूद हैं: अन्यथा हमें हर समय मैन्युअल रूप से साफ होने वाली प्रक्रियाओं का एक गुच्छा मिलेगा।

उबंटू 18.04 पर परीक्षण किया गया।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.