O sigprocmask() não está funcionando corretamente?

O sigprocmask() não está funcionando corretamente?

(Desculpe pela postagem longa, mas queria ser o mais preciso possível)

Eu estava tentando imprimir a máscara de sinal do thread principal enquanto escrevia um programa C, quando me deparei com algo estranho sobre como a sigprocmaskfunção funciona.

Fundo[Fonte: página de manual sigprocmask(2)]
A sigprocmaskfunção é usada para buscar e/ou alterar a máscara de sinal do thread de chamada.

/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • Se oldsetnão for NULL, o valor anterior da máscara de sinal é armazenado em oldset.
  • Se setfor NULL, então a máscara de sinal permanece inalterada (ou seja, howé ignorada), mas o valor atual da máscara de sinal é retornado oldset(se não for NULL).
  • Um conjunto de funções para modificar e inspecionar variáveis ​​do tipo sigset_t("conjuntos de sinais") é descrito em sigsetops(3). No exemplo:
    • int sigemptyset(sigset_t *set);: inicializa o sinal definido por set paravazio, com todos os sinais excluídos do conjunto.
    • int sigfillset(sigset_t *set);: inicializa definido comocompleto, incluindo todos os sinais.
    • int sigismember(const sigset_t *set, int signum);: testa se signum é membro do conjunto.

Observação: Ao criar um conjunto de sinais preenchidos, a sigfillsetfunção glibc não inclui odoissinais em tempo real usados ​​internamente pela implementação de threading NPTL.

Especificidades do sistema
Distribuição Linux: Linux Mint 19.3Cinnamon
Versão Glibc: 2.27(padrão)
Também verificado para versão Glibc:2.31.9

Saída de 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

Replicação de problemas

O programa que me alarmou com a possibilidade de algo dar errado é o seguinte:

#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");
}

A função print_set_binimprime a saída de sigismember( 0para não um membro, 1para um membro) para todos os sinais. A definição macro NSIG(= 65) signal.hé o maior número do sinal mais um (1), conforme mencionado em /usr/include/x86_64-linux-gnu/bits/signum-generic.h. No mesmo arquivo, também é mencionado que este maior número de sinal inclui sinais em tempo real (faixa de números [32, 64]) e que o número de sinal zero (0) é reservado para fins de teste.

Como resultado, meu código postado acima testa números de sinal que pertencem ao intervalo [1, 64].

Abaixo segue osaídado programa:

NSIG = 65

Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]

Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]

After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]

Explicação de saída
Neste programa, uma variável setdo tipo sigset_té manipulada. Inicialmente, a função sigemptyseté usada para definir todos os bits como zero, depois a função sigfillseté usada para definir todos os bits como um (exceto dois; consulteObservaçãoemFundoseção) e finalmente sigprocmaské usado para armazenar a máscara do sinal atual na mesma variável. Após todas as operações realizadas no conjunto de sinais, a função print_set_biné usada para imprimir quais sinais pertencem ao conjunto e se o conjunto está vazio (usando sigisemptyset()).

O problema parece ser a última chamada de print_set_bin, onde não é encontrado nenhum sinal pertencente ao conjunto, mas a sigisemptysetfunção caracteriza o conjunto como não vazio. Isso me fez pensar se sigset_tcontém mais de 64 bits e pelo menos um deles é diferente de zero.

Pesquisar
Rastreando os arquivos de cabeçalho incluídos em signal.h, descobri que sigset_té uma estrutura definida /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.hcomo __sigset_t.he typedefed em /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;

No começo pensei que 1024 bits eram demais, mas depois me deparei comesta respostapara outra pergunta unix.stackexchange.com. Decidi então usar os detalhes de implementação da sigset_testrutura para imprimir todos os 1024 bits. Fiz isso no código a seguir, substituindo function print_set_binpor function print_set_wordque imprime todos _SIGSET_NWORDS(= 16) unsigned long ints alocados no __valarray.

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");
}

Programasaída:

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]

Notas:

  • 18446744067267100671== 0b1111111111111111111111111111111001111111111111111111111111111111(64 bits definidos como 1, exceto dois; consulteObservaçãoemFundoseção)
  • 18446744073709551615== 0b1111111111111111111111111111111111111111111111111111111111111111(64 bits definidos como 1)

Explicação e pergunta de saída
Como você pode ver, sigemptyset()manipule sigfillset()todos os 1024 bits do conjunto. Chamar sigemptyset()em vez de sigfillset()antes de chamar sigprocmask()revela que sigprocmask()manipula apenas os primeiros 64 bits (one unsigned long int), deixando os 1024-64=960 bits restantes intocados! E aí vem a tão esperada pergunta: isso não é um bug? Não deveria sigprocmask()escrever em todos os dados da estrutura?

Responder1

Sim, sigprocmask()não estava funcionando corretamente!

Em 11 de março de 2020 preenchi um novo relatório de bug no rastreador de bugs da glibc. Há alguns minutos, o status do bug foi alterado para Resolvido/Corrigido na versão glibc 2.32.

Muito obrigado aos desenvolvedores da glibc envolvidos na resolução deste bug!

informação relacionada