web-dev-qa-db-ja.com

sigprocmask()は正しく機能していませんか?

(長い投稿で申し訳ありませんが、できるだけ正確になりたかった)

sigprocmask関数がどのように機能するかについて奇妙なことに遭遇したとき、私はCプログラムの作成中にメインスレッドのシグナルマスクを出力しようとしていました。

背景[ソース:マニュアルページsigprocmask(2)]
sigprocmask関数は、呼び出しスレッドのシグナルマスクをフェッチまたは変更するために使用されます。

/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • oldsetNULL以外の場合、シグナルマスクの以前の値はoldsetに格納されます。
  • setNULLの場合、シグナルマスクは変更されません(つまり、howは無視されます)が、シグナルマスクの現在の値はoldsetに返されます(NULLでない場合)。
  • sigset_t型の変数を変更および検査するための関数のセット(「信号セット」)は、sigsetops(3)で説明されています。例:
    • int sigemptyset(sigset_t *set);:すべてのシグナルがセットから除外された状態で、setによって指定されたシグナルセットをemptyに初期化します。
    • int sigfillset(sigset_t *set);:すべての信号を含め、フルに初期化します。
    • int sigismember(const sigset_t *set, int signum);:signumがセットのメンバーであるかどうかをテストします。

Note:満たされた信号セットを作成する場合、glibc sigfillset関数には、NPTLスレッド実装によって内部的に使用される2つのリアルタイム信号が含まれません。

システム仕様
Linuxディストリビューション:Linux Mint 19.3 Cinnamon
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)の出力を出力します。 signal.hで述べたように、/usr/include/x86_64-linux-gnu/bits/signum-generic.hのマクロ定義NSIG(= 65)は、最大のシグナル番号に1を加えたものです。同じファイルで、この最大信号番号にはリアルタイム信号(番号範囲[32、64])が含まれ、その信号番号ゼロ(0)はテスト用に予約されていることも記載されています。

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

以下はプログラムの出力に従います:

NSIG = 65

Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]

Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]

After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]

出力移植
このプログラムでは、タイプsigset_tの変数setが操作されます。最初に、関数sigemptysetを使用してすべてのビットをゼロに設定し、次に関数sigfillsetを使用してすべてのビットを1に設定します(2つを除く)。Note背景セクションを参照)そして最後に、sigprocmaskは、現在の信号マスクを同じ変数に格納するために使用されます。シグナルセットですべての操作が行われた後、関数print_set_binを使用して、セットに属するシグナルとセットが空かどうかを出力します(sigisemptyset()を使用)。

問題は、print_set_binへの最後の呼び出しのようです。この場合、セットに属する信号は見つかりませんが、sigisemptyset関数は、セットを空ではないものとして特徴付けます。そのため、sigset_tが64ビットを超えており、少なくとも1つがゼロでないかどうかを考えました。

研究
signal.hに含まれるヘッダーファイルを追跡すると、sigset_t/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h__sigset_t.hとして定義され、typedefedは/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_binprint_set_Word配列に割り当てられたすべての_SIGSET_NWORDS(= 16)unsigned long intsを出力する関数__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に設定。背景のNoteを参照セクション)
  • 18446744073709551615 == 0b1111111111111111111111111111111111111111111111111111111111111111(64ビットを1に設定)

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

4
gzach

はい、sigprocmask()は正しく機能していませんでした!

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

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

1
gzach