Функция 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равно NULL, то маска сигнала не изменяется (т.е. howигнорируется), но текущее значение маски сигнала тем не менее возвращается в oldset(если оно не равно NULL).
  • Набор функций для изменения и проверки переменных типа sigset_t(«наборы сигналов») описан в sigsetops(3). Например:
    • int sigemptyset(sigset_t *set);: инициализирует набор сигналов, заданный параметром set toпустой, при этом все сигналы исключены из набора.
    • int sigfillset(sigset_t *set);: инициализирует наборполный, включая все сигналы.
    • int sigismember(const sigset_t *set, int signum);: проверяет, является ли signum членом множества.

Примечание: При создании заполненного набора сигналов sigfillsetфункция glibc не включаетдвасигналы реального времени, используемые внутри реализации потоковой передачи 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выводит вывод sigismember( 0для не члена, 1для члена) для всех сигналов. Макроопределение NSIG(= 65) в signal.h— это наибольший номер сигнала плюс один (1), как указано в /usr/include/x86_64-linux-gnu/bits/signum-generic.h. В том же файле также упоминается, что этот наибольший номер сигнала включает сигналы реального времени (диапазон номеров [32, 64]) и что номер сигнала ноль (0) зарезервирован для целей тестирования.

В результате мой код, размещенный выше, проверяет номера сигналов, которые принадлежат диапазону [1, 64].

Ниже следуетвыходпрограммы:

NSIG = 65

Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]

Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]

After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]

Выходное объяснение
В этой программе выполняется манипуляция переменной setтипа sigset_t. Сначала функция sigemptysetиспользуется для установки всех битов в ноль, затем функция sigfillsetиспользуется для установки всех битов в единицу (кроме двух; см.ПримечаниевФонраздел) и, наконец, sigprocmaskиспользуется для сохранения текущей маски сигнала в той же переменной. После всех операций, выполненных над набором сигналов, функция print_set_binиспользуется для вывода того, какие сигналы принадлежат набору и является ли набор пустым (используя sigisemptyset()).

Проблема, похоже, в последнем вызове print_set_bin, где не найдено ни одного сигнала, принадлежащего набору, но sigisemptysetфункция характеризует набор как непустой. Это заставило меня задуматься, sigset_tсодержит ли более 64 бит и хотя бы один из них не равен нулю.

Исследовать
Прослеживая заголовочные файлы, включенные в signal.h, я обнаружил, что sigset_tесть структура, определенная в /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.hи __sigset_t.hed typedefв /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 бита. Я сделал это в следующем коде, заменив функцию print_set_binна функцию print_set_word, которая печатает все _SIGSET_NWORDS(= 16) unsigned 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 бита установлены в 1, за исключением двух; см.ПримечаниевФонраздел)
  • 18446744073709551615== 0b1111111111111111111111111111111111111111111111111111111111111111(64 бита установлены в 1)

Пояснение вывода и вопрос
Как вы можете видеть, sigemptyset()и sigfillset()манипулирует всеми 1024 битами набора. Вызов sigemptyset()вместо sigfillset()перед вызовом sigprocmask()показывает, что sigprocmask()манипулирует только первыми 64 битами (один unsigned long int), оставляя оставшиеся 1024-64=960 бит нетронутыми! И вот тут возникает долгожданный вопрос: не ошибка ли это? Не следует ли sigprocmask()записывать во все данные структуры?

решение1

Да, sigprocmask()работало некорректно!

11-мар-2020 я заполнил новый отчет об ошибке на баг-трекере glibc. Несколько минут назад статус ошибки был изменен на Resolved/Fixed as of glibc version 2.32.

Большое спасибо разработчикам glibc, принявшим участие в устранении этой ошибки!

Связанный контент