일반적으로, 우리는 같은 신호 생성 프로세스를 종료한다 SIGKILL
, SIGTSTP
등
그러나 누가 특정 신호를 주문했는지, 누가 특정 프로세스로 전송했는지, 그리고 일반적으로 신호가 어떻게 동작을 수행하는지는 어떻게 알 수 있습니까? 신호는 내부적으로 어떻게 작동합니까?
일반적으로, 우리는 같은 신호 생성 프로세스를 종료한다 SIGKILL
, SIGTSTP
등
그러나 누가 특정 신호를 주문했는지, 누가 특정 프로세스로 전송했는지, 그리고 일반적으로 신호가 어떻게 동작을 수행하는지는 어떻게 알 수 있습니까? 신호는 내부적으로 어떻게 작동합니까?
답변:
50,000 피트 뷰는 다음과 같습니다.
신호는 내부적으로 커널에 의해 (예를 들어, SIGSEGV
유효하지 않은 주소에 액세스하거나 +를 SIGQUIT
눌렀을 때 ) 또는 syscall (또는 여러 관련 주소 )을 사용하는 프로그램에 의해 생성됩니다 .Ctrl\kill
syscall 중 하나 인 경우 커널은 호출 프로세스에 신호를 보낼 수있는 충분한 권한이 있는지 확인합니다. 그렇지 않으면 오류가 반환되고 신호가 발생하지 않습니다.
두 가지 특수 신호 중 하나 인 경우 대상 프로세스의 입력없이 커널이 무조건 작동합니다. 두 가지 특수 신호는 SIGKILL 및 SIGSTOP입니다. 기본 동작, 신호 차단 등에 대한 아래의 모든 내용은이 두 가지와 관련이 없습니다.
다음으로, 커널은 신호로 무엇을하는지 알아냅니다.
각 프로세스마다 각 신호와 관련된 동작이 있습니다. 이 기본 설정의 무리가 있으며, 프로그램은 다른 사람이 사용하여 설정할 수 있습니다 sigaction
, signal
등 이들은 "프로세스를 중지", "코어 덤프와 프로세스를 종료", "프로세스를 종료", "완전히 무시"등이 포함 기타
또한 프로그램은 신호별로 신호 전달 ( "차단")을 해제 할 수 있습니다. 그런 다음 신호는 차단 해제 될 때까지 보류 상태를 유지합니다.
프로그램은 커널이 자체적으로 조치를 취하는 대신 , 프로세스가 수행하는 모든 작업을 중단하고 지정된 함수를 호출 하여 신호를 프로세스에 동 기적으로 ( sigwait
등을 사용 하여 signalfd
) 또는 비동기 적으로 전달하도록 요청할 수 있습니다 .
"실시간 신호"라고하는 두 번째 신호 세트는 특별한 의미가 없으며 여러 신호를 대기시킬 수도 있습니다 (정상 신호는 신호가 차단 될 때 각 신호 중 하나만 대기합니다). 스레드가 서로 통신하기 위해 다중 스레드 프로그램에서 사용됩니다. 예를 들어 glibc의 POSIX 스레드 구현에 여러 가지가 사용됩니다. 또한 서로 다른 프로세스간에 통신하는 데 사용할 수도 있습니다 (예 : 여러 실시간 신호를 사용하여 fooctl 프로그램이 foo 데몬에 메시지를 보내도록 할 수 있음).
5 만 피트가 아닌 경우에는 man 7 signal
커널 내부 문서 (또는 소스)를 사용해보십시오 .
신호 구현은 매우 복잡하며 커널에 따라 다릅니다. 다시 말해, 다른 커널은 신호를 다르게 구현할 것입니다. 간단한 설명은 다음과 같습니다.
특수 레지스터 값을 기반으로하는 CPU는 실제로 벡터 테이블 인 "인터럽트 디스크립터 테이블"을 찾을 것으로 예상되는 메모리에 주소를 가지고 있습니다. 0으로 나누기 또는 INT 3 (디버그)과 같은 트랩과 같이 가능한 모든 예외에 대해 하나의 벡터가 있습니다. CPU에서 예외가 발생하면 플래그와 현재 명령어 포인터를 스택에 저장 한 다음 관련 벡터로 지정된 주소로 이동합니다. Linux에서이 벡터는 항상 예외 처리기가있는 커널을 가리 킵니다. 이제 CPU가 완료되고 Linux 커널이 대신합니다.
소프트웨어에서 예외를 트리거 할 수도 있습니다. 예를 들어, 사용자가 CTRL-를 누르면 C이 호출은 자체 예외 처리기를 호출하는 커널로 이동합니다. 일반적으로 핸들러에 도달하는 다른 방법이 있지만 동일한 기본 사항에 관계없이 컨텍스트가 스택에 저장되고 커널의 예외 핸들러로 이동합니다.
그런 다음 예외 처리기는 신호를 수신 할 스레드를 결정합니다. 0으로 나누기와 같은 것이 발생하면 쉽습니다. 예외를 발생시킨 스레드가 신호를 얻지 만 다른 유형의 신호의 경우 결정이 매우 복잡 할 수 있으며 비정상적인 경우 다소 임의의 스레드가 발생할 수 있습니다. 신호를 얻으십시오.
커널이하는 일을 신호로 보내려면 먼저 신호 유형 등을 나타내는 값을 설정하십시오 SIGHUP
. 이것은 단지 정수입니다. 모든 프로세스에는이 값이 저장되는 "보류중인 신호"메모리 영역이 있습니다. 그런 다음 커널은 신호 정보로 데이터 구조를 만듭니다. 이 구조는 디폴트, 무시 또는 처리 될 수있는 신호 "처리"를 포함한다. 그런 다음 커널은 자체 함수를 호출합니다 do_signal()
. 다음 단계가 시작됩니다.
do_signal()
첫 번째는 여부를 결정 가 신호를 처리합니다. 예를 들어 그것이 kill 인 do_signal()
경우 프로세스 종료 프로세스를 종료합니다. 그렇지 않으면 처분을 봅니다. 배치가 기본값 인 do_signal()
경우 신호에 따라 달라지는 기본 정책에 따라 신호 를 처리합니다. 처리가 처리되면, 사용자 프로그램에 해당 신호를 처리하도록 설계된 기능이 있으며이 기능에 대한 포인터가 위에서 언급 한 데이터 구조에 있음을 의미합니다. 이 경우 do_signal ()은 다른 커널 함수를 호출합니다.handle_signal()
그런 다음 사용자 모드로 다시 전환하고이 함수를 호출하는 프로세스를 거칩니다. 이 핸드 오프의 세부 사항은 매우 복잡합니다. 프로그램의이 코드는 일반적으로의 기능을 사용할 때 프로그램에 자동으로 연결됩니다 signal.h
.
보류중인 신호 값을 적절하게 검사하여 커널은 프로세스가 모든 신호를 처리하고 있는지 확인하고 그렇지 않은 경우 적절한 조치를 취하여 신호에 따라 프로세스를 휴면 상태로 만들거나 종료시킬 수 있습니다.
이 질문에 대한 답변이 있지만 Linux 커널에 자세한 이벤트 흐름을 게시하겠습니다.
이것은 Linux 게시물 : Linux Signals – 내부
(sklinuxblog.blogspot.in의“Linux 게시물”블로그)에서 완전히 복사 한 것입니다.
간단한 신호 사용자 공간 C 프로그램을 작성하는 것으로 시작하겠습니다.
#include<signal.h>
#include<stdio.h>
/* Handler function */
void handler(int sig) {
printf("Receive signal: %u\n", sig);
};
int main(void) {
struct sigaction sig_a;
/* Initialize the signal handler structure */
sig_a.sa_handler = handler;
sigemptyset(&sig_a.sa_mask);
sig_a.sa_flags = 0;
/* Assign a new handler function to the SIGINT signal */
sigaction(SIGINT, &sig_a, NULL);
/* Block and wait until a signal arrives */
while (1) {
sigsuspend(&sig_a.sa_mask);
printf("loop\n");
}
return 0;
};
이 코드는 SIGINT 신호에 새로운 핸들러를 할당합니다. Ctrl+ C키 조합을 사용하여 SIGINT를 실행중인 프로세스로 보낼 수 있습니다 . 때 Ctrl+ C다음을 누르면 비동기 신호 SIGINT는 작업에 전송됩니다. kill -INT <pid>
다른 터미널 에서 명령 을 보내는 것과 같습니다 .
당신이 할 경우 kill -l
(즉, 소문자의 L
"목록"의 약자) 당신은 실행중인 프로세스에 전송 될 수있는 다양한 신호를 알게됩니다.
[root@linux ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
또한 다음 키 조합을 사용하여 특정 신호를 보낼 수 있습니다.
위의 C 프로그램을 컴파일하고 실행하면 다음과 같은 결과가 나타납니다.
[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop
와도 Ctrl + C또는 kill -2 <pid>
프로세스가 종료되지 않습니다. 대신 신호 처리기를 실행하고 반환합니다.
신호의 내부가 프로세스로 전송되는 것을보고 dump_stack과 함께 Jprobe를 __send_signal
함수에 놓으면 다음과 같은 호출 추적이 나타납니다.
May 5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May 5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May 5 16:18:37 linux kernel: complete_signal+0x205/0x250
May 5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May 5 16:18:37 linux kernel: send_signal+0x3e/0x80
May 5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May 5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May 5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May 5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May 5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May 5 16:18:37 linux kernel: ? ftrace_ops_list_func+0x106/0x120
May 5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May 5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May 5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May 5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May 5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May 5 16:18:37 linux kernel: kthread+0xcf/0xe0
May 5 16:18:37 linux kernel: kthread_create_on_node+0x140/0x140
May 5 16:18:37 linux kernel: ret_from_fork+0x7c/0xb0
May 5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140
따라서 신호 전송을위한 주요 함수 호출은 다음과 같습니다.
First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state() -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.
이제 모든 것이 설정되고 필요한 변경이 task_struct
프로세스에 .
신호는 시스템 호출에서 리턴 될 때 또는 인터럽트에서 리턴 할 때 프로세스에 의해 점검 / 처리됩니다. 시스템 호출로부터의 리턴은 파일에 있습니다.entry_64.S
.
int_signal 함수가 entry_64.S
호출되어 함수를 호출합니다.do_notify_resume()
.
기능을 확인합시다 do_notify_resume()
. 이 함수는 TIF_SIGPENDING
플래그가 다음에 설정되어 있는지 확인 합니다 task_struct
.
/* deal with pending signal delivery */
if (thread_info_flags & _TIF_SIGPENDING)
do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;
"느린"syscall (예 : 읽기 / 쓰기 차단, 프로세스를 대기 상태로 전환) :
TASK_INTERRUPTIBLE
또는TASK_UNINTERRUPTIBLE
.
상태에있는 작업은 신호에 의해 상태 TASK_INTERRUPTIBLE
로 변경됩니다 TASK_RUNNING
.TASK_RUNNING
프로세스를 예약 할 수 있음을 의미합니다.
실행되면 "느린"syscall이 완료되기 전에 신호 처리기가 실행됩니다. 그만큼syscall
기본적으로 완료되지 않습니다.
만약 SA_RESTART
플래그가 설정되어,syscall
신호 처리기 완료된 후 다시 시작된다.
kill
쉘 내장 명령 인 경우 제외 ). (2c) }
함수를 닫은 후 세미콜론 은 엄격하게 말해서 오류는 아니지만 불필요하고 정통하지 않습니다. (3) 모든 것이 정확하더라도 그 질문에 대한 답은 그리 좋지 않을 것입니다. (3a) 질문은 다소 불분명하지만 행위자 (사용자 및 프로세스) 가 신호를 시작 (즉, 전송 ) 하는 방법에 초점을 맞추고있는 것 같습니다 . … (계속)