(Entschuldigen Sie den langen Beitrag, aber ich wollte so genau wie möglich sein.)
Ich habe beim Schreiben eines C-Programms versucht, die Signalmaske des Hauptthreads auszudrucken, als mir hinsichtlich der Funktionsweise der sigprocmask
Funktion etwas Merkwürdiges auffiel.
Hintergrund[Quelle: Manualpage sigprocmask(2)
]
Die sigprocmask
Funktion wird verwendet, um die Signalmaske des aufrufenden Threads abzurufen und/oder zu ändern.
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- Wenn
oldset
nicht - istNULL
, wird der vorherige Wert der Signalmaske in gespeichertoldset
. - Wenn
set
istNULL
, dann bleibt die Signalmaske unverändert (d. h.how
wird ignoriert), aber der aktuelle Wert der Signalmaske wird trotzdem zurückgegebenoldset
(wenn dies nicht der Fall istNULL
). - Eine Reihe von Funktionen zum Ändern und Überprüfen von Variablen des Typs
sigset_t
(„Signalsätze“) wird in beschriebensigsetops(3)
. Im Beispiel:int sigemptyset(sigset_t *set);
: initialisiert das von set gegebene Signalset aufleer, wobei alle Signale aus dem Satz ausgeschlossen sind.int sigfillset(sigset_t *set);
: initialisiert gesetzt aufvoll, einschließlich aller Signale.int sigismember(const sigset_t *set, int signum);
: testet, ob Signum ein Mitglied des Sets ist.
Notiz: Beim Erstellen eines gefüllten Signalsatzes sigfillset
berücksichtigt die glibc-Funktion nicht diezweiEchtzeitsignale, die intern von der NPTL-Threading-Implementierung verwendet werden.
Systemspezifikationen
Linux-Distribution: Linux Mint 19.3
Cinnamon
Glibc-Version: 2.27
(Standard)
Auch überprüft für Glibc-Version:2.31.9
Ausgabe von 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
Problemreplikation
Das folgende Programm hat mich alarmiert, dass möglicherweise etwas schief läuft:
#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");
}
Die Funktion print_set_bin
gibt die Ausgabe von sigismember
( 0
für kein Mitglied, 1
für ein Mitglied) für alle Signale aus. Die Makrodefinition NSIG
(= 65) in signal.h
ist die größte Signalnummer plus eins (1), wie in erwähnt /usr/include/x86_64-linux-gnu/bits/signum-generic.h
. In derselben Datei wird auch erwähnt, dass diese größte Signalnummer Echtzeitsignale (Nummernbereich [32, 64]) umfasst und dass die Signalnummer Null (0) für Testzwecke reserviert ist.
Als Ergebnis testet mein oben veröffentlichter Code nach Signalnummern, die in den Bereich [1, 64] gehören.
Nachfolgend finden Sie dieAusgabedes Programms:
NSIG = 65
Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]
Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]
After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]
Ausgabeerklärung
In diesem Programm wird die Variable set
vom Typ sigset_t
manipuliert. Zuerst sigemptyset
werden mit der Funktion alle Bits auf Null gesetzt, dann sigfillset
werden mit der Funktion alle Bits auf Eins gesetzt (außer zwei; sieheNotizInHintergrundAbschnitt) und sigprocmask
wird schließlich verwendet, um die aktuelle Signalmaske in derselben Variablen zu speichern. Nachdem alle Operationen am Signalsatz ausgeführt wurden, print_set_bin
wird die Funktion verwendet, um auszudrucken, welche Signale zum Satz gehören und ob der Satz leer ist (mit sigisemptyset()
).
Das Problem scheint der letzte Aufruf von zu sein print_set_bin
, bei dem kein Signal gefunden wird, das zum Set gehört, die sigisemptyset
Funktion das Set aber als nicht leer charakterisiert. Das brachte mich zum Nachdenken, ob sigset_t
mehr als 64 Bits enthält und mindestens eines davon ungleich Null ist.
Forschung
Beim Verfolgen der in enthaltenen Headerdateien signal.h
habe ich festgestellt, dass es sigset_t
sich um eine Struktur handelt, die in /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h
als definiert __sigset_t.h
und typedef
in bearbeitet wird /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;
Zuerst dachte ich, dass 1024 Bits zu viel seien, aber dann stieß ich aufdiese Antwortzu einer anderen Frage auf unix.stackexchange.com. Ich habe mich dann entschieden, die Implementierungsdetails der sigset_t
Struktur zu verwenden, um alle 1024 Bits auszudrucken. Ich habe dies im folgenden Code getan, indem ich die Funktion print_set_bin
durch eine Funktion ersetzt habe, die alle dem Array zugewiesenen (= 16) s print_set_word
ausgibt ._SIGSET_NWORDS
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");
}
ProgrammAusgabe:
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]
Anmerkungen:
18446744067267100671
==0b1111111111111111111111111111111001111111111111111111111111111111
(64 Bits auf 1 gesetzt, außer zwei; sieheNotizInHintergrundAbschnitt)18446744073709551615
==0b1111111111111111111111111111111111111111111111111111111111111111
(64 Bit auf 1 gesetzt)
Ausgabeerklärung und Frage
Wie Sie sehen, sigemptyset()
werden sigfillset()
alle 1024 Bits des Satzes manipuliert. Der Aufruf sigemptyset()
statt sigfillset()
vor dem Aufruf sigprocmask()
zeigt, dass sigprocmask()
nur die ersten 64 Bits (eins unsigned long int
) manipuliert werden und die restlichen 1024-64=960 Bits unberührt bleiben! Und hier kommt die lang erwartete Frage: Ist das nicht ein Fehler? Sollte nicht sigprocmask()
in die gesamten Strukturdaten geschrieben werden?
Antwort1
Ja, sigprocmask()
hat nicht richtig funktioniert!
Am 11. März 2020 habe ich einen neuen Fehlerbericht im glibc-Bugtracker eingereicht. Vor ein paar Minuten wurde der Fehlerstatus ab der glibc-Version auf „Gelöst/Behoben“ geändert 2.32
.
- Fehlerbericht:https://sourceware.org/bugzilla/show_bug.cgi?id=25657
- Fehlerbehebung (Commit):https://sourceware.org/git/?p=glibc.git;a=commit;h=566e10aa7292bacd74d229ca6f2cd9e8c8ba8748
Vielen Dank an die glibc-Entwickler, die an der Behebung dieses Fehlers beteiligt waren!