sigprocmask()가 올바르게 작동하지 않습니까?

sigprocmask()가 올바르게 작동하지 않습니까?

(글이 길어서 죄송하지만 최대한 정확하게 전달하고 싶었습니다.)

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무시 됨) 그럼에도 불구하고 신호 마스크의 현재 값은 에 반환됩니다 ( 가 아닌 경우 ).NULLhowoldsetNULL
  • 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.3Cinnamon
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은 테스트 목적으로 예약되어 있다고 언급되어 있습니다.sigismember01NSIGsignal.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_t64비트보다 많은 비트를 보유하고 있고 그 중 적어도 하나가 0이 아닌지 생각하게 되었습니다 .

연구
에 포함된 헤더 파일을 추적해 보면 as 및 ed 에 정의된 구조체가 있음 signal.h을 발견했습니다 .sigset_t/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h__sigset_t.htypedef/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_NWORDSunsigned 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 intsigprocmask()

답변1

예, sigprocmask()제대로 작동하지 않았습니다!

2020년 3월 11일에 glibc 버그 추적기에 대한 새로운 버그 보고서를 작성했습니다. 몇 분 전 glibc 버전부터 버그 상태가 Resolved/Fixed로 변경되었습니다 2.32.

이 버그를 해결하는 데 참여한 glibc 개발자에게 많은 감사를 드립니다!

관련 정보