code

Ctrl-C는 어떻게 자식 프로세스를 종료합니까?

codestyles 2020. 11. 28. 09:31
반응형

Ctrl-C는 어떻게 자식 프로세스를 종료합니까?


CTRL+ C가 자식을 종료하는 방법을 이해하려고 노력하고 있지만 부모 프로세스는 아닙니다. bash장기 실행 프로세스를 시작한 다음 CTRL- 를 입력하여 종료 할 수 있는 일부 스크립트 셸에서이 동작을 확인 C하고 컨트롤이 셸로 돌아갑니다.

어떻게 작동하는지, 특히 부모 (쉘) 프로세스가 종료되지 않는 이유를 설명해 주시겠습니까?

쉘은 CTRL+ C이벤트에 대한 특별한 처리를 해야합니까? 그렇다면 정확히 무엇을합니까?


기본적으로 신호는 커널에 의해 처리됩니다. 구형 Unix 시스템에는 15 개의 신호가있었습니다. 이제 그들은 더 많이 있습니다. 확인 </usr/include/signal.h>(또는 kill -l) 할 수 있습니다 . CTRL+ C는 이름이있는 신호입니다 SIGINT.

각 신호를 처리하기위한 기본 동작은 커널에도 정의되어 있으며 일반적으로 신호를 수신 한 프로세스를 종료합니다.

모든 신호 (그러나 SIGKILL)는 프로그램에서 처리 할 수 ​​있습니다.

그리고 이것은 쉘이하는 일입니다 :

  • 셸이 대화 형 모드에서 실행될 때이 모드에 대한 특수 신호 처리가 있습니다.
  • 예를 들어 프로그램을 실행할 때 find셸은 다음과 같습니다.
    • forks 자체
    • 자식의 경우 기본 신호 처리를 설정합니다.
    • 자식을 주어진 명령 (예 : find)으로 바꿉니다.
    • CTRL+ 를 누르면 C부모 쉘이이 신호를 처리하지만 자식은 기본 동작으로 종료합니다. (아이도 신호 처리를 구현할 수 있습니다)

trap쉘 스크립트에서도 신호를 보낼 수 있습니다 .

또한 대화 형 셸에 대한 신호 처리를 설정할 수도 있습니다 ~/.profile. 맨 위에 입력 해보세요 . (이미 로그인했는지 확인하고 다른 터미널로 테스트하십시오. 직접 잠글 수 있습니다.)

trap 'echo "Dont do this"' 2

이제 쉘에서 CTRL+ 를 누를 때마다 C메시지가 인쇄됩니다. 줄을 제거하는 것을 잊지 마십시오!

관심이 있다면 여기/bin/sh 에서 소스 코드에서 일반 이전 신호 처리를 확인할 수 있습니다 .

위의 주석 (현재 삭제됨)에 잘못된 정보가 있었으므로 여기에 관심이있는 사람 은 신호 처리 작동 방식에 대한 매우 좋은 링크 있습니다.


먼저 POSIX 터미널 인터페이스에 대한 Wikipedia 기사를 끝까지 읽으 십시오 .

SIGINT신호는 단말 제어 규칙에 의해 생성하고, 상기 단말의 모든 프로세스에 방송되는 전경 프로세스 기 . 셸은 이미 실행 한 명령 (또는 명령 파이프 라인)에 대한 새 프로세스 그룹을 생성했으며 터미널에 해당 프로세스 그룹이 (터미널의) 포 그라운드 프로세스 그룹임을 알 렸습니다. 모든 동시 명령 파이프 라인에는 자체 프로세스 그룹이 있으며 포 그라운드 명령 파이프 라인은 쉘이 터미널의 포 그라운드 프로세스 그룹으로 터미널에 프로그래밍 한 프로세스 그룹이있는 것입니다. 전경과 배경 사이에서 "작업"을 전환하는 것은 (일부 세부 사항은 제외하고) 어떤 프로세스 그룹이 이제 전경 그룹인지 터미널에 알리는 셸 문제입니다.

쉘 프로세스 자체는 자체적으로 또 다른 프로세스 그룹에 있으므로 해당 프로세스 그룹 중 하나가 포 그라운드에있을 때 신호를 수신하지 않습니다. 그렇게 간단합니다.


터미널은 현재 터미널에 연결된 프로세스로 INT (인터럽트) 신호를 보냅니다. 그런 다음 프로그램은이를 수신하고이를 무시하거나 종료 할 수 있습니다.

강제로 닫히는 프로세스는 없습니다 (기본적으로 sigint를 처리하지 않으면 동작이를 호출한다고 생각 abort()하지만 조회해야 함).

물론 실행중인 프로세스는 해당 프로세스를 시작한 셸과 분리되어 있습니다.

당신이 경우 원하는 갈 수있는 부모 쉘을, 당신의 프로그램을 실행합니다 exec:

exec ./myprogram

이렇게하면 부모 셸이 자식 프로세스로 대체됩니다.


setpgid POSIX C 프로세스 그룹 최소 예

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

이것은 자식이 프로세스 그룹을 변경하지 않은 경우 신호가 자식에게 전송되는 방법을 보여줍니다 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 업스트림 .

다음으로 컴파일 :

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

then the child changes its pgid, and now only a single sigint gets printed every time from the parent only:

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

And now, whenever you hit:

Ctrl + C

only the parent receives the signal as well:

sigint parent

You can still kill the parent as before with a SIGQUIT:

Ctrl + \

however the child now has a different PGID, and does not receive that signal! This can seen from:

ps aux | grep setpgid

You will have to kill it explicitly with:

kill -9 16470

This makes it clear why signal groups exist: otherwise we would get a bunch of processes left over to be cleaned manually all the time.

Tested on Ubuntu 18.04.


CTRL+C is a map to the kill command. When you press them, kill sends a SIGINT signal, that's interrupts the process.

Kill : http://en.wikipedia.org/wiki/Kill_(command)

SIGINT : http://en.wikipedia.org/wiki/SIGINT_(POSIX)

참고 URL : https://stackoverflow.com/questions/6108953/how-does-ctrl-c-terminate-a-child-process

반응형