(글이 길어서 죄송하지만 최대한 정확하게 전달하고 싶었습니다.)
C 프로그램을 작성하면서 메인 스레드의 신호 마스크를 인쇄하려고 하다가 함수 sigprocmask
작동 방식과 관련하여 이상한 점을 발견했습니다.
배경[출처: 매뉴얼 페이지 sigprocmask(2)
]
이 sigprocmask
함수는 호출 스레드의 신호 마스크를 가져오거나 변경하는 데 사용됩니다.
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
oldset
가 아닌 경우NULL
신호 마스크의 이전 값이 에 저장됩니다oldset
.- 이면 신호 마스크는 변경되지 않지만(즉,
set
무시 됨) 그럼에도 불구하고 신호 마스크의 현재 값은 에 반환됩니다 ( 가 아닌 경우 ).NULL
how
oldset
NULL
sigset_t
유형의 변수 ("신호 세트") 를 수정하고 검사하기 위한 함수 세트는 에 설명되어 있습니다sigsetops(3)
. 예를 들어:int sigemptyset(sigset_t *set);
: set에 의해 주어진 신호 세트를 초기화합니다.비어 있는, 모든 신호가 세트에서 제외됩니다.int sigfillset(sigset_t *set);
: 초기화 설정가득한, 모든 신호를 포함합니다.int sigismember(const sigset_t *set, int signum);
: signum이 set의 멤버인지 테스트합니다.
메모: 채워진 신호 세트를 생성할 때 glibc sigfillset
함수는둘NPTL 스레딩 구현에서 내부적으로 사용되는 실시간 신호입니다.
시스템 사양
Linux 배포판: Linux Mint 19.3
Cinnamon
Glibc 버전: 2.27
(기본값)
Glibc 버전에 대해서도 확인되었습니다.2.31.9
출력 uname -a
:
Linux 5.0.0-32-generic #34~18.04.2-Ubuntu SMP Thu Oct 10 10:36:02 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
문제 복제
나에게 뭔가 잘못될 가능성이 있음을 경고한 프로그램은 다음과 같습니다.
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#define BUFFER_SIZE 32
#define OUTOFBOUNDS
#undef OUTOFBOUNDS
void print_set_bin(sigset_t *setp);
int main(void)
{
sigset_t set;
printf("NSIG = %d\n\n", NSIG);
printf("Empty set:\n");
if (sigemptyset(&set))
{
perror("sigemptyset");
return -1;
}
print_set_bin(&set);
printf("Filled set:\n");
if (sigfillset(&set))
{
perror("sigfillset");
return -1;
}
print_set_bin(&set);
printf("After sigprocmask():\n");
if (sigprocmask(SIG_BLOCK, NULL, &set))
{
perror("sigprocmask");
return -1;
}
print_set_bin(&set); // Why non-empty?
return 0;
}
void print_set_bin(sigset_t *setp)
{
int sig, res;
char buff[BUFFER_SIZE];
if (!setp)
{
fprintf(stderr, "print_set_bin(): NULL parameter\n");
return;
}
#ifdef OUTOFBOUNDS
for (sig = 0; sig <= NSIG; sig++)
#else
for (sig = 1; sig < NSIG; sig++)
#endif
{
res = sigismember(setp, sig);
if (res == -1)
{
snprintf(buff, BUFFER_SIZE, "sigisimember [%d]", sig);
perror(buff);
}
else
printf("%d", res);
}
printf(" [%s]\n\n", sigisemptyset(setp) ? "Empty" : "Non-empty");
}
함수는 모든 신호에 대해 ( 멤버가 아닌 경우, 멤버의 경우) print_set_bin
출력을 인쇄합니다 . 의 매크로 정의 (= 65)는 에서 언급한 것처럼 가장 큰 신호 번호에 1을 더한 것입니다 . 동일한 파일에서 이 가장 큰 신호 번호에는 실시간 신호(번호 범위 [32, 64])가 포함되며 신호 번호 0은 테스트 목적으로 예약되어 있다고 언급되어 있습니다.sigismember
0
1
NSIG
signal.h
/usr/include/x86_64-linux-gnu/bits/signum-generic.h
결과적으로 위에 게시된 내 코드는 [1, 64] 범위에 속하는 신호 번호를 테스트합니다.
아래는 다음과 같습니다산출프로그램의:
NSIG = 65
Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]
Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]
After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]
출력 설명
이 프로그램에서는 set
타입의 변수를 sigset_t
조작합니다. 처음에는 함수를 sigemptyset
사용하여 모든 비트를 0으로 설정한 다음 sigfillset
모든 비트를 1로 설정하는 데 사용합니다(2를 제외하고, 참조).메모~에배경섹션) 마지막으로 sigprocmask
현재 신호 마스크를 동일한 변수에 저장하는 데 사용됩니다. 신호 세트에 대해 모든 작업이 수행된 후 기능은 print_set_bin
어떤 신호가 세트에 속하는지, 세트가 비어 있는지 여부를 인쇄하는 데 사용됩니다( 사용 sigisemptyset()
).
문제는 print_set_bin
집합에 속하는 신호가 발견되지 않는 에 대한 마지막 호출인 것 같지만 sigisemptyset
함수는 집합이 비어 있지 않은 것으로 특성화합니다. sigset_t
64비트보다 많은 비트를 보유하고 있고 그 중 적어도 하나가 0이 아닌지 생각하게 되었습니다 .
연구
에 포함된 헤더 파일을 추적해 보면 as 및 ed 에 정의된 구조체가 있음 signal.h
을 발견했습니다 .sigset_t
/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h
__sigset_t.h
typedef
/usr/include/x86_64-linux-gnu/bits/types/sigset_t.h
#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
처음에는 1024비트가 너무 많다고 생각했지만 나중에는이 답변다른 unix.stackexchange.com 질문에. 그런 다음 구조체의 구현 세부 정보를 사용하여 sigset_t
모든 1024비트를 인쇄하기로 결정했습니다. 다음 코드에서 함수를 배열 에 할당된 모든 (= 16) 을 인쇄하는 print_set_bin
함수로 대체하여 수행했습니다 .print_set_word
_SIGSET_NWORDS
unsigned long int
__val
void print_set_word(sigset_t *setp)
{
int i;
if (!setp)
{
fprintf(stderr, "print_set_word(): NULL parameter\n");
return;
}
for (i = 0; i < 16; i++)
{
printf("%lu\n", setp->__val[i]);
}
printf("[%s]\n\n", sigisemptyset(setp) ? "Empty" : "Non-empty");
}
프로그램산출:
Empty set:
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
[Empty]
Filled set:
18446744067267100671
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
[Non-empty]
After sigprocmask():
0
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
[Non-empty]
노트:
18446744067267100671
==0b1111111111111111111111111111111001111111111111111111111111111111
(64비트는 2를 제외하고 1로 설정됨; 참조)메모~에배경부분)18446744073709551615
==0b1111111111111111111111111111111111111111111111111111111111111111
(64비트가 1로 설정됨)
출력 설명 및 질문
보시다시피 sigemptyset()
세트 sigfillset()
의 모든 1024비트를 조작합니다. 호출하기 전 sigemptyset()
대신 호출 하면 처음 64비트(one )만 조작하고 나머지 1024-64=960비트는 그대로 유지됩니다! 그리고 오랫동안 기다려온 질문이 있습니다. 이것은 버그가 아닌가요? 전체 구조체 데이터에 쓰면 안 되나요 ?sigfillset()
sigprocmask()
sigprocmask()
unsigned long int
sigprocmask()
답변1
예, sigprocmask()
제대로 작동하지 않았습니다!
2020년 3월 11일에 glibc 버그 추적기에 대한 새로운 버그 보고서를 작성했습니다. 몇 분 전 glibc 버전부터 버그 상태가 Resolved/Fixed로 변경되었습니다 2.32
.
- 버그 신고:https://sourceware.org/bugzilla/show_bug.cgi?id=25657
- 버그 수정(커밋):https://sourceware.org/git/?p=glibc.git;a=commit;h=566e10aa7292bacd74d229ca6f2cd9e8c8ba8748
이 버그를 해결하는 데 참여한 glibc 개발자에게 많은 감사를 드립니다!