¿Sigprocmask() no funciona correctamente?

¿Sigprocmask() no funciona correctamente?

(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 sigprocmaskfunciona la función.

Fondo[Fuente: página del manual sigprocmask(2)]
La sigprocmaskfunció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 oldsetno es NULL, el valor anterior de la máscara de señal se almacena en oldset.
  • Si setes NULL, entonces la máscara de señal no cambia (es decir, howse ignora), pero el valor actual de la máscara de señal se devuelve oldset(si no es NULL).
  • 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 sigfillsetfunció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.3Cinnamon
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_binimprime la salida de sigismember( 0para no un miembro, 1para un miembro) para todas las señales. La definición de macro NSIG(= 65) signal.hes 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 setde tipo . sigset_tAl principio, la función sigemptysetse usa para establecer todos los bits en cero, luego la función sigfillsetse usa para establecer todos los bits en uno (excepto dos; consulteNotaenFondosección) y finalmente sigprocmaskse 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_binse 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 sigisemptysetfunción caracteriza el conjunto como no vacío. Eso me hizo pensar si sigset_tcontiene 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_tes una estructura definida en /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.has __sigset_t.hy typedefed 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_testructura para imprimir los 1024 bits. Lo hice en el código siguiente, reemplazando la función print_set_bincon una función print_set_wordque imprime todos _SIGSET_NWORDS(= 16) unsigned long intlos asignados a __valla 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.

¡Muchas gracias a los desarrolladores de glibc involucrados en la resolución de este error!

información relacionada