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で指定された信号セットを初期化します空のすべての信号がセットから除外されます。
    • int sigfillset(sigset_t *set);: 設定を初期化します満杯すべての信号を含みます。
    • int sigismember(const sigset_t *set, int signum);: signum が set のメンバーであるかどうかをテストします。

注記: 満たされたシグナルセットを作成する場合、glibcsigfillset関数は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の出力を出力します。 のマクロ定義(= 65) は、で説明されているように、最大​​シグナル番号に 1 を加えたものです。同じファイルには、この最大シグナル番号にはリアルタイム シグナル (番号範囲 [32, 64]) が含まれ、シグナル番号 0 はテスト目的で予約されていることも記載されています。sigismember01NSIGsignal.h/usr/include/x86_64-linux-gnu/bits/signum-generic.h

その結果、上記のコードは範囲[1, 64]に属する信号番号をテストします。

以下は出力プログラムの詳細:

NSIG = 65

Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]

Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]

After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]

出力の説明
このプログラムでは、set型の変数sigset_tを操作します。最初に関数sigemptysetを使用してすべてのビットを0に設定し、次に関数sigfillsetを使用してすべてのビットを1に設定します(2を除く。注記背景セクション)で説明され、最後に、sigprocmask現在のシグナル マスクを同じ変数に格納するために使用されます。シグナル セットに対してすべての操作が実行された後、関数をprint_set_bin使用して、どのシグナルがセットに属しているか、およびセットが空であるかどうかを出力します( を使用sigisemptyset())。

問題は、 の最後の呼び出しにあるようです。この呼び出しでprint_set_binは、セットに属するシグナルが見つかりませんが、関数はセットを空ではないものとして特徴付けます。そこで、が 64 ビット以上を保持し、そのうちの少なくとも 1 つはゼロではないのではないsigisemptysetかと考えました。sigset_t

研究
に含まれるヘッダー ファイルをトレースすると、が で として定義され、でed されている構造体であるsignal.hことがわかりました。sigset_t/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h__sigset_t.htypedef/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 の別の質問に回答します。次に、構造体の実装の詳細を使用して、1024 ビットすべてを出力することにしました。次のコードで、関数を、配列に割り当てられたすべての(= 16)を出力する関数にsigset_t置き換えて実行しました。print_set_binprint_set_word_SIGSET_NWORDSunsigned 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ビットは2ビットを除いて1に設定されています。注記背景セクション)
  • 18446744073709551615== 0b1111111111111111111111111111111111111111111111111111111111111111(64 ビットを 1 に設定)

出力の説明と質問
ご覧のとおり、sigemptyset()と はsigfillset()セットの 1024 ビットすべてを操作します。を呼び出す前にsigemptyset()の代わりにを呼び出すと、 は最初の 64 ビット (1 つ) のみを操作し、残りの 1024-64=960 ビットはそのまま残すことがわかります。ここで、待ちに待った質問が来ます。これはバグではないでしょうか。構造体データ全体に書き込むべきではないでしょうか。sigfillset()sigprocmask()sigprocmask()unsigned long intsigprocmask()

答え1

はい、sigprocmask()正しく動作していませんでした。

2020 年 3 月 11 日に、glibc バグ トラッカーに新しいバグ レポートを入力しました。数分前に、バグのステータスが glibc バージョン時点で解決済み/修正済みに変更されました2.32

このバグの解決に携わった glibc 開発者の方々に深く感謝いたします。

関連情報