(投稿が長くて申し訳ありませんが、できるだけ正確にしたかったです)
Cプログラムを書いている間、メインスレッドのシグナルマスクを印刷しようとしたときに、sigprocmask
関数の仕組みで奇妙なことがわかりました。
背景[ソース:Man Page 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 がセットのメンバーであるかどうかをテストします。
ノート:パディングされた信号セットを生成するとき、glibc関数にはsigfillset
次のものは含まれません。二つNPTLスレッドは、内部使用のためのリアルタイム信号を実装します。
システムの詳細
Linux ディストリビューション: Linux Mint 19.3
Cinnamon Glibc
バージョン: (2.27
既定値)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
を使用してすべてのビットを0に設定し、次にsigfillset
すべてのビットを1に設定する関数を使用します(2を除く、参照)。ノート存在する背景セクション)最後に、sigprocmask
現在の信号マスクを同じ変数に保存するために使用されます。シグナルセットに対するすべての操作を実行したら、print_set_bin
この機能を使用して、そのセットに属するシグナルとセットが空であるか(使用済みsigisemptyset()
)を印刷します。
問題は、print_set_bin
セットに属するシグナルが見つからないという最後の呼び出しであるようですが、関数はセットが空ではないとsigisemptyset
説明します。これはsigset_t
64ビット以上であり、そのうちの少なくとも1つはゼロでないことを考えました。
研究
埋め込みヘッダファイルを追跡してみると、これがasとedで定義された構造であるsignal.h
ことがわかりました。sigset_t
/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h
__sigset_t.h
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
配列に割り当てられたすべて(= 16)を印刷する関数に置き換えました。print_set_word
_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");
}
プログラム出力:
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 int
sigprocmask()
答え1
はい、sigprocmask()
正常に動作しません!
2020年3月11日、私はglibcのバグトラッカーに関する新しいバグレポートを作成しました。数分前、glibcバージョンからエラーステータスが「Resolved / Fixed」に変更されました2.32
。
- エラーレポート:https://sourceware.org/bugzilla/show_bug.cgi?id=25657
- バグ修正(コミット):https://sourceware.org/git/?p=glibc.git;a=commit;h=566e10aa7292bacd74d229ca6f2cd9e8c8ba8748
このバグの解決に参加したglibc開発者に感謝します!