(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 sigprocmask
função funciona.
Fundo[Fonte: página de manual sigprocmask(2)
]
A sigprocmask
funçã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
oldset
não forNULL
, o valor anterior da máscara de sinal é armazenado emoldset
. - Se
set
forNULL
, então a máscara de sinal permanece inalterada (ou seja,how
é ignorada), mas o valor atual da máscara de sinal é retornadooldset
(se não forNULL
). - Um conjunto de funções para modificar e inspecionar variáveis do tipo
sigset_t
("conjuntos de sinais") é descrito emsigsetops(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 sigfillset
funçã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.3
Cinnamon
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_bin
imprime a saída de sigismember
( 0
para não um membro, 1
para 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 set
do 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 sigisemptyset
função caracteriza o conjunto como não vazio. Isso me fez pensar se sigset_t
conté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.h
como __sigset_t.h
e typedef
ed 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_t
estrutura para imprimir todos os 1024 bits. Fiz isso no código a seguir, substituindo function print_set_bin
por function print_set_word
que imprime todos _SIGSET_NWORDS
(= 16) unsigned long int
s alocados no __val
array.
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
.
- Relatório de erro:https://sourceware.org/bugzilla/show_bug.cgi?id=25657
- Correção de bug (confirmação):https://sourceware.org/git/?p=glibc.git;a=commit;h=566e10aa7292bacd74d229ca6f2cd9e8c8ba8748
Muito obrigado aos desenvolvedores da glibc envolvidos na resolução deste bug!