(포스팅이 길어서 죄송합니다만 최대한 정확하고 싶었습니다)
C 프로그램을 작성하면서 메인 스레드의 시그널 마스크를 인쇄하려고 했을 때 sigprocmask
함수 작동 방식에서 이상한 점을 발견했습니다.
배경[출처: Man Page 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이 세트의 구성원인지 테스트합니다.
노트: 패딩된 신호 세트를 생성할 때 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
모든 시그널의 출력을 인쇄합니다 sigismember
( 0
비회원의 경우, 1
회원의 경우). NSIG
(= 65) 의 매크로 정의는 signal.h
에 설명된 대로 최대 신호 수에 1을 더한 것입니다 /usr/include/x86_64-linux-gnu/bits/signum-generic.h
. 같은 문서에서는 이 최대 신호 번호에 라이브 신호(번호 범위 [32, 64])가 포함되며 신호 번호 0이 테스트 목적으로 예약되어 있다고 언급되어 있습니다.
따라서 위에 게시된 내 코드는 [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비트를 모두 인쇄하기로 결정했습니다 . 아래 코드에서 이 작업을 수행하여 함수를 print_set_bin
배열에 할당된 모든(=16)을 인쇄하는 함수로 대체했습니다.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비트(1개 )에서만 작동하고 나머지 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 개발자에게 많은 감사를 드립니다!