SIGINT가 상위 프로세스로 전송될 때 하위 프로세스로 전파되지 않는 이유는 무엇입니까?

SIGINT가 상위 프로세스로 전송될 때 하위 프로세스로 전파되지 않는 이유는 무엇입니까?

셸 프로세스(예: sh)와 해당 하위 프로세스(예: cat)가 주어지면 셸의 프로세스 ID를 사용하여 + 동작을 어떻게 시뮬레이션할 수 있습니까 Ctrl?C


내가 시도한 것은 다음과 같습니다.

sh그런 다음 실행하십시오 cat.

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

SIGINTcat다른 터미널 에서 보내기:

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

cat신호가 수신되고 종료됩니다(예상대로).

상위 프로세스에 신호를 보내는 것이 작동하지 않는 것 같습니다.신호가 cat상위 프로세스로 전송될 때 전파되지 않는 이유 는 무엇입니까 sh?

이것은 작동하지 않습니다:

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

답변1

CTRL+ C작동 방식

가장 먼저 이해해야 할 것은 CTRL+가 어떻게 C작동하는지입니다.

CTRL+ 를 누르면 C터미널 에뮬레이터가 ETX 문자(End of Text/0x03)를 보냅니다.
TTY는 이 문자를 수신할 때 SIGINT를 터미널의 전경 프로세스 그룹으로 보내도록 구성됩니다. 이 구성은 를 실행 stty -a하고 보면 볼 수 있습니다 intr = ^C;. 이것POSIX 사양INTR이 수신되면 SIGINT를 터미널의 포그라운드 프로세스 그룹으로 보내야 함을 나타냅니다.

포그라운드 프로세스 그룹이란 무엇입니까?

이제 문제는 전경 프로세스 그룹이 무엇인지 결정하는 방법입니다. 전경 프로세스 그룹은 단순히 키보드에서 생성된 모든 신호(SIGTSTP, SIGINT 등)를 수신하는 프로세스 그룹입니다.

프로세스 그룹 ID를 확인하는 가장 쉬운 방법은 다음을 사용하는 것입니다 ps.

ps ax -O tpgid

두 번째 열은 프로세스 그룹 ID입니다.

프로세스 그룹에 신호를 보내는 방법은 무엇입니까?

이제 프로세스 그룹 ID가 무엇인지 알았으므로 전체 그룹에 신호를 보내는 POSIX 동작을 시뮬레이션해야 합니다.

kill그룹 ID를 앞에 추가 하면 됩니다 . 예를 들어 프로세스 그룹 ID가 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)

그러면 포그라운드 프로세스 그룹에 SIGINT가 전송됩니다 $tty.  

답변2

~처럼vinc17 라고, 이런 일이 일어날 이유가 없습니다. 신호 생성 키 시퀀스(예: Ctrl+ ) 를 입력하면 C신호는 터미널에 연결된(연관된) 모든 프로세스로 전송됩니다. 생성된 신호에는 그러한 메커니즘이 없습니다 kill.

그러나 이와 같은 명령은

kill -SIGINT -12345

모든 프로세스에 신호를 보냅니다.프로세스 그룹12345;보세요죽이다(1) 그리고죽이다(2). 쉘의 하위 프로세스는 일반적으로 쉘의 프로세스 그룹에 있으므로(적어도 비동기적이지 않은 경우) 음수 쉘 PID에 신호를 보내면 원하는 대로 작동할 수 있습니다.


이런

~처럼vinc17이 지적했습니다., 이는 대화형 쉘에서는 작동하지 않습니다. 이것은 대안입니다가능한일하다:

kill -SIGINT -$(echo $(ps -p쉘 PIDtpgid=))

ps -pPID_of_shell쉘에 대한 프로세스 정보를 얻으십시오.  헤더 없이 터미널 프로세스 그룹 ID만 출력되어야 함 o tpgid=을 나타냅니다 . ps10000 미만인 경우 ps선행 공백이 표시됩니다. 이는 $(echo …)선행(및 후행) 공백을 제거하는 빠른 방법입니다.

나는 데비안 머신에서 대략적인 테스트를 했습니다.

답변3

이 질문에는 자체 답변이 포함되어 있습니다. Send SIGINTto catprocess는 kill+를 눌렀을 때 일어나는 일을 완벽하게 시뮬레이션합니다.CtrlC

보다 정확하게는 인터럽트 문자가 ^C(기본적으로) SIGINT터미널의 전경 프로세스 그룹에 있는 모든 프로세스로 전송됩니다. cat여러 프로세스가 포함된 더 복잡한 명령을 실행하는 경우 ^C.

백그라운드 연산자 없이 외부 명령을 실행하면 &쉘은 명령에 대한 새 프로세스 그룹을 생성하고 프로세스 그룹이 이제 포그라운드에 있음을 터미널에 알립니다. 쉘은 자체 프로세스 그룹에 남아 있으며 더 이상 포그라운드에 있지 않습니다. 그런 다음 쉘은 명령이 종료될 때까지 기다립니다.

여기서 일반적인 오해, 즉 쉘이 자식 프로세스와 터미널 사이의 상호 작용을 촉진하기 위해 무언가를 하고 있다는 생각의 희생양이 되는 것 같습니다. 그것은 사실이 아닙니다. 설정(프로세스 생성, 터미널 모드 설정, 파이프 생성 및 기타 파일 설명자 리디렉션, 대상 프로그램 실행)이 완료되면 셸그냥 기다리고 있어. cat일반 입력이든 신호를 생성하는 특수 문자(예: ) 이든 입력한 내용은 셸을 통해 전달되지 않습니다 ^C. 프로세스는 자신의 파일 디스크립터를 통해 터미널에 직접 접근할 수 있으며, 포그라운드 프로세스 그룹이기 때문에 cat터미널은 프로세스에 직접 신호를 보낼 수 있다 . cat껍질이 벗겨졌습니다.

프로세스가 종료되면 cat해당 프로세스의 상위 프로세스인 쉘에 알림이 전송됩니다 cat. 그러면 쉘이 활성화되어 다시 전경으로 나타납니다.

이해도를 높이는 연습입니다.

새 터미널의 쉘 프롬프트에서 다음 명령을 실행합니다.

exec cat

이 키워드를 사용하면 하위 프로세스를 생성하지 않고 exec쉘이 실행됩니다 . cat쉘은 으로 대체됩니다 cat. 이전에 쉘에 속했던 PID는 이제 의 PID입니다 cat. ps다른 터미널에서 이를 확인하세요. 임의의 줄을 입력하고 cat반복적으로 표시되는 것을 확인하면 상위 프로세스인 쉘 프로세스가 없어도 여전히 정상적으로 작동한다는 것을 알 수 있습니다.지금 +를 누르면 Ctrl어떻게 되나요 C?

답변:

SIGINT는 종료되는 cat 프로세스로 전달됩니다. 터미널의 유일한 프로세스이기 때문에 쉘 프롬프트에서 "exit"라고 말한 것처럼 세션이 종료됩니다. 사실 고양이예전에는잠시 동안 당신의 껍질.

답변4

setpgidPOSIX C 프로세스 그룹 최소 예

기본 API의 실행 가능한 최소 예제를 통해 이해하는 것이 더 쉬울 수 있습니다.

이는 자식 프로세스가 프로세스 그룹을 변경하지 않은 경우 자식 프로세스에 신호를 보내는 방법을 보여줍니다 setpgid.

메인 프로그램

#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 업스트림.

엮다:

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는 기본적으로 전체 프로세스 그룹에 kill 명령을 보냅니다.

SIGQUIT와 같이 두 프로세스에 서로 다른 신호를 보내 프로그램을 종료합니다 Ctrl + \.

다음으로 실행setpgid

예를 들어 매개변수를 사용하여 실행하는 경우:

./setpgid 1

그런 다음 하위 프로세스는 pgid를 변경하고 이제 한 번에 상위 프로세스에서 하나의 sigint만 인쇄합니다.

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

이제 클릭할 때마다 다음이 수행됩니다.

Ctrl + C

부모만이 신호를 받습니다:

sigint parent

이전과 같이 SIGQUIT를 사용하여 상위 항목을 종료할 수 있습니다.

Ctrl + \

그러나 이제 해당 하위에는 다른 PGID가 있으므로 신호를 받을 수 없습니다! 이는 다음과 같은 측면에서 볼 수 있습니다.

ps aux | grep setpgid

명시적으로 종료해야 합니다.

kill -9 16470

이는 신호 그룹이 존재하는 이유를 명확하게 보여줍니다. 그렇지 않으면 수동으로 정리해야 하는 프로세스가 많이 남게 됩니다.

우분투 18.04에서 테스트되었습니다.

관련 정보