(Perdón por la extensión del post pero quería ser lo más preciso posible)
Estaba intentando imprimir la máscara de señal del hilo principal mientras escribía un programa en C, cuando me encontré con algo extraño sobre cómo sigprocmask
funciona la función.
Fondo[Fuente: página del manual sigprocmask(2)
]
La sigprocmask
función se utiliza para buscar y/o cambiar la máscara de señal del hilo que llama.
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- Si
oldset
no esNULL
, el valor anterior de la máscara de señal se almacena enoldset
. - Si
set
esNULL
, entonces la máscara de señal no cambia (es decir,how
se ignora), pero el valor actual de la máscara de señal se devuelveoldset
(si no esNULL
). - En . se describe un conjunto de funciones para modificar e inspeccionar variables de tipo
sigset_t
("conjuntos de señales")sigsetops(3)
. Por ejemplo:int sigemptyset(sigset_t *set);
: inicializa la señal configurada dada por set tovacío, con todas las señales excluidas del set.int sigfillset(sigset_t *set);
: inicializa establecido enlleno, incluidas todas las señales.int sigismember(const sigset_t *set, int signum);
: prueba si signum es miembro del conjunto.
Nota: Al crear un conjunto de señales completo, la sigfillset
función glibc no incluye eldosseñales en tiempo real utilizadas internamente por la implementación de subprocesos NPTL.
Detalles del sistema
Distribución de Linux: Linux Mint 19.3
Cinnamon
Versión de Glibc: 2.27
(predeterminada)
También verificado para la versión de Glibc:2.31.9
Salida 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
Replicación de problemas
El programa que me alarmó de que posiblemente algo saliera mal es el siguiente:
#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");
}
La función print_set_bin
imprime la salida de sigismember
( 0
para no un miembro, 1
para un miembro) para todas las señales. La definición de macro NSIG
(= 65) signal.h
es el número de señal más grande más uno (1) como se menciona en /usr/include/x86_64-linux-gnu/bits/signum-generic.h
. En el mismo archivo, también se menciona que este número de señal más grande incluye señales en tiempo real (rango de números [32, 64]) y que el número de señal cero (0) está reservado para fines de prueba.
Como resultado, mi código publicado arriba prueba los números de señal que pertenecen al rango [1, 64].
A continuación sigue elproducciónDel programa:
NSIG = 65
Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]
Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]
After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]
Explicación de salida
En este programa se manipula variable set
de tipo . sigset_t
Al principio, la función sigemptyset
se usa para establecer todos los bits en cero, luego la función sigfillset
se usa para establecer todos los bits en uno (excepto dos; consulteNotaenFondosección) y finalmente sigprocmask
se utiliza para almacenar la máscara de señal actual en la misma variable. Después de todas las operaciones que tienen lugar en el conjunto de señales, la función print_set_bin
se utiliza para imprimir qué señales pertenecen al conjunto y si el conjunto está vacío (usando sigisemptyset()
).
El problema parece ser la última llamada a print_set_bin
, donde no se encuentra ninguna señal que pertenezca al conjunto, pero la sigisemptyset
función caracteriza el conjunto como no vacío. Eso me hizo pensar si sigset_t
contiene más de 64 bits y al menos uno de ellos es distinto de cero.
Investigación
Al rastrear los archivos de encabezado incluidos en signal.h
, descubrí que sigset_t
es una estructura definida en /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h
as __sigset_t.h
y typedef
ed en /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;
Al principio pensé que 1024 bits eran demasiados, pero luego me encontré conesta respuestaa otra pregunta de unix.stackexchange.com. Luego decidí usar los detalles de implementación de la sigset_t
estructura para imprimir los 1024 bits. Lo hice en el código siguiente, reemplazando la función print_set_bin
con una función print_set_word
que imprime todos _SIGSET_NWORDS
(= 16) unsigned long int
los asignados a __val
la matriz.
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");
}
Programaproducción:
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 establecidos en 1 excepto dos; consulteNotaenFondosección)18446744073709551615
==0b1111111111111111111111111111111111111111111111111111111111111111
(64 bits establecidos en 1)
Explicación de salida y pregunta
Como puedes ver, sigemptyset()
manipula sigfillset()
todos los 1024 bits del conjunto. Llamar sigemptyset()
en lugar de sigfillset()
llamar antes sigprocmask()
revela que sigprocmask()
manipula solo los primeros 64 bits (uno unsigned long int
), ¡dejando intactos los 1024-64=960 bits restantes! Y aquí viene la tan esperada pregunta: ¿No es esto un error? ¿No debería sigprocmask()
escribir en todos los datos de la estructura?
Respuesta1
¡Sí, sigprocmask()
no estaba funcionando correctamente!
El 11 de marzo de 2020 completé un nuevo informe de error en el rastreador de errores de glibc. Hace unos minutos, el estado del error se cambió a Resuelto/Reparado a partir de la versión glibc 2.32
.
- Informe de error:https://sourceware.org/bugzilla/show_bug.cgi?id=25657
- Corrección de errores (compromiso):https://sourceware.org/git/?p=glibc.git;a=commit;h=566e10aa7292bacd74d229ca6f2cd9e8c8ba8748
¡Muchas gracias a los desarrolladores de glibc involucrados en la resolución de este error!