신호 및 비동기 신호 안전하지 않은 기능에 대해 배우고 있습니다. 특히, 이는 printf
비동기 신호에 안전하지 않으며 기본 프로그램 스레드 및 신호 처리기에서 호출될 때 교착 상태를 일으킬 수 있다는 것을 알게 되었습니다. 이를 확인하기 위해 다음 프로그램을 작성했습니다(약간 보기 흉함).
/*
* sig-deadlock.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void
sigint_handler(int signum)
{
int i;
printf("Signal");
for (i = 0; i < 30; i++) {
printf("%d\n", i);
}
}
int
main(void)
{
int i;
printf("PID: %d\n", getpid());
signal(SIGINT, sigint_handler);
for (i = 0; i < 1e9; i++) {
printf("a");
sleep(1);
}
return EXIT_SUCCESS;
}
나는 프로그램이 내부 루프를 실행하여(따라서 호출 printf
) SIGINT를 보낼 때마다(사용하여 kill -INT $PID
) 30개의 신호 처리기를 실행하기를 원합니다.
그러나 실행하는 동안 신호 처리기가 한 번 실행되고 다음 신호가 프로세스를 종료하는 것을 관찰했습니다. 이 동작의 원인과 해결 방법은 무엇입니까?
운영 체제: 리눅스 4.9.0.
답변1
signal(2)
이전 신호 API의 일부이며 항상 사용하기 편리한 것은 아닙니다. 주로 다음과 같은 이유 때문입니다.
signal()의 유일한 휴대용 사용은 신호의 처리를
SIG_DFL
또는 로 설정하는 것입니다SIG_IGN
. 신호 처리기를 설정할 때 사용되는 의미는signal()
시스템마다 다릅니다(그리고 POSIX.1은 이러한 변형을 명시적으로 허용합니다).이러한 목적으로 사용하지 마십시오.원래 UNIX 시스템에서는
signal()
신호 전달을 통해 핸들러가 호출되었을 때,신호 처리가 다음으로 재설정됩니다.SIG_DFL
, 시스템은 추가 신호 인스턴스를 차단하지 않습니다. 시스템 V는 여전히signal()
.BSD에서는 신호 처리기가 호출될 때 신호 처리가 재설정되지 않으며 처리기가 실행되는 동안 신호의 다른 인스턴스가 전달되지 않도록 차단됩니다.
Linux의 상황은 다음과 같습니다.
- 커널의
signal()
시스템 호출은 System V 의미 체계를 제공합니다.- 기본적으로 glibc 2 이상에서는
signal()
래퍼 함수 [...]가sigaction(2)
BSD 의미 체계를 제공하는 플래그와 함께 호출됩니다.- glibc 2 이상에서는
_BSD_SOURCE
기능 테스트 매크로가 정의되지 않은 경우signal()
System V 의미 체계가 제공됩니다. ( 표준 모드 중 하나에서 호출되는 경우_BSD_SOURCE
기본 암시적 정의가 제공되지 않습니다 . 또는... 참조gcc(1)
-std=xxx
-ansi
signal(2)
.
즉, 많은 경우에 설정된 핸들러는 두 번 이상 사용되지 않습니다. 신호 후에 정의한 함수는 삭제되고 프로세스가 종료 signal
되는 경우 핸들러는 기본값으로 재설정됩니다 .SIGINT
방금 인용한 매뉴얼 페이지를 읽어보시기를 권장하지만, 이 상황을 처리하는 가장 좋은 방법은 사용을 중단 signal
하고 로 전환하는 것 sigaction
입니다. 매뉴얼 페이지에서 필요한 모든 세부 정보를 얻을 수 있지만 다음은 간단한 예입니다.
void sighandler(int signum);
int main(void) {
struct sigaction sa;
sa.sa_handler = sighandler;
sigemptyset(&(sa.sa_mask));
sigaddset(&(sa.sa_mask), SIGINT);
sigaction(SIGINT, &sa, NULL);
int i;
for(i = 0; i < 5; i++) {
printf("Sleeping...\n");
sleep(5);
printf("Woke up!\n");
}
}
void sighandler(int signum) {
printf("Signal caught!\n");
}
그러나 중요한 참고 사항이 있습니다.이미 알고 있듯이(따라서 함수의 "무한" 루프) 신호는 프로그램에서 신호를 처리하는지 여부에 관계없이 main
신호가 수신되면 시스템 호출(예: 호출)을 중단합니다 . sleep(3)
위의 예에서 프로그램은~ 할 것이다인쇄일어났다!이렇게 할 때마다 kill
수면 호출이 조기에 종료됩니다. 매뉴얼 페이지를 보면 호출이 중단 시 남은 절전 시간(초)을 반환해야 함을 알 수 있습니다.
답변2
여기서 강조되지 않은 또 다른 전제 조건이 있습니다.
일관된 결과를 보장하려면 sigaction 구조에서 sa_flags를 설정해야 합니다.
sa.sa_flags = 0 ; // this works for me
동일한 방식으로 신호를 등록하는 2개의 서로 다른 응용 프로그램이 있고 그 중 하나만 이 문제가 있었기 때문에 몇 분 동안 당황했습니다(신호를 한 번만 포착함).
잠시 동안 내 코드를 살펴보고 매뉴얼 페이지를 다시 읽은 후에 두 애플리케이션에 대한 함수 스택 부작용의 서로 다른 뉘앙스로 인해 서로 다른 동작이 발생한다는 것을 깨달았습니다.
메모리를 할당하거나 중첩된 함수를 입력하기 전에 main()의 매우 초기에 sigaction()을 사용하여 신호 처리기를 등록하는 프로그램(이것은 불규칙한 sigaction 동작을 갖는 프로그램입니다).
메인 상단에 메모리 등을 차지하는 다른 변수를 선언한 후 sigaction()이라는 또 다른 프로그램이 있는데, sa.sa_flags의 메모리를 0(또는 0으로 초기화할 수 있는 숫자)으로 일관된 동작을 제공하는 우연이 있는 것처럼 보였습니다. SA_RESETHAND 비트가 설정되어 있지 않습니다).
일관되지 않은 응용 프로그램이 때때로 등록된 신호를 초기화하고 동작을 한 번 포착하고 때로는 모든 동작을 포착한다는 사실은 쓰레기 등으로 인해 일종의 임의성이 발생할 수 있음을 깨달았습니다.
John의 답변에 있는 간단한 예제 조각 규칙은 아직 불완전하며 sa_flags 설정이 생략되어 일관되지 않은 결과를 제공할 수 있습니다.