[英]C Signal handler doesn't uninstall when installed from another function
我正在使用以下代碼片段體驗我認為是一些奇怪的行為。 當我打電話addHandler()
安裝信號處理,信號處理程序被調用每一次我按CTRL+C
在終端(發送SIGINT),但是如果我更換調用addHandler()
與內容addHandler()
函數(當前已注釋掉),處理程序只被調用一次(據我所知,這是預期的行為),隨后的 SIGINT 將實際終止進程,因為沒有安裝用戶處理程序。 我在這里錯過了一些基本的東西嗎? 為什么通過另一個函數安裝處理程序,似乎是永久安裝它?
我敢肯定它比那更微妙……但這是代碼:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigHandler(int signal) {
printf("Signal handler beiing called!\n");
}
void addHandler() {
struct sigaction actionStruct;
actionStruct.sa_handler = sigHandler;
sigaction(SIGINT, &actionStruct, NULL);
}
int main() {
addHandler();
/*struct sigaction actionStruct;
actionStruct.sa_handler = sigHandler;
sigaction(SIGINT, &actionStruct, NULL);*/
int i = 0;
while (1) {
printf("In while with i: %d\n", i++);
sleep(1);
}
return 0;
}
謝謝!
聲明時不會清除struct sigaction actionStruct
的內存。 您設置了一個值。 結構體的其余部分將包含來自前一個函數的堆棧中的值。 這可能就是為什么不同的函數有不同的行為。
您需要使用struct sigaction actionStruct = {};
聲明它struct sigaction actionStruct = {};
或使用memset
我在這里錯過了一些基本的東西嗎?
是的。
您沒有正確初始化struct sigaction actionStruct
。 本質上,您提供了隨機.sa_flags
,這會導致 OP 觀察到的問題。
推薦的初始化方法是使用memset()
和sigemptyset()
:
memset(&actionStruct, 0, sizeof actionStruct); sigemptyset(&actionStruct.sa_mask);
memset()
將整個結構清零,包括任何填充。 sigemptyset()
清除在信號處理程序本身運行時阻塞的信號集; 即,它將其初始化為空集)。
您沒有設置actionStruct.sa_flags
成員。
零對於它來說是一個完全有效的值,但明確設置它對我們人類很重要,因為這樣我們就可以讀取意圖。
例如,如果您只希望處理程序運行一次,則可以設置actionStruct.sa_flags = SA_RESETHAND;
. 傳遞第一個信號后, SA_RESETHAND
標志會導致處理程序重置為默認值。 對於SIGINT
,這是Term (終止進程),如man 7 信號手冊頁中所述。
printf()
不是異步信號安全函數(如較新系統中的man 7 信號安全手冊頁,舊系統中的man 7 信號手冊頁中所列)。
取決於確切的 C 庫實現(有許多 POSIXy 系統),它可能會工作,可能會出現亂碼,甚至可能會導致進程崩潰。 所以,不要那樣做。
親愛的讀者,希望您對編寫健壯的、可移植的、POSIX 信號處理 C99 或更高版本的程序真正感興趣,讓我向您展示一個示例。 breakme.c :
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
/* Low-level, async-signal safe write routines.
*/
static int wrpp(const int descriptor, const char *ptr, const char *end)
{
while (ptr < end) {
ssize_t n = write(descriptor, ptr, (size_t)(end - ptr));
if (n > 0)
ptr += n;
else
if (n != -1)
return EIO; /* Should not occur */
else
if (errno != EINTR)
return errno;
}
return 0;
}
static int wrs(const int descriptor, const char *s)
{
if (descriptor == -1)
return EBADF;
else
if (!s)
return EINVAL;
else {
/* Note: strlen() is not listed as an async-signal safe function. */
const char *end = s;
while (*end)
end++;
return wrpp(descriptor, s, end);
}
}
static int wrn(const int descriptor, const char *ptr, const size_t len)
{
if (descriptor == -1)
return EBADF;
else
return wrpp(descriptor, ptr, ptr + len);
}
static int wri(const int descriptor, const long value)
{
char buffer[4 + (sizeof value) * (CHAR_BIT * 10) / 3];
char *const end = buffer + sizeof buffer;
char *ptr = buffer + sizeof buffer;
unsigned long u = (value < 0) ? -value : value;
if (descriptor == -1)
return EBADF;
do {
*(--ptr) = '0' + (u % 10);
u /= 10uL;
} while (u);
if (value < 0)
*(--ptr) = '-';
return wrpp(descriptor, ptr, end);
}
/* 'Done' signal handler.
*/
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
int saved_errno;
/* Note: Most commonly, we just use
done = 1;
here. In practice, we could also just use
done = signum;
because current POSIXy systems don't have a signal 0.
The following uses signum if it is nonzero,
and -1 for (signum == 0).
*/
done = (signum) ? signum : -1;
/* Before running functions that affect errno, save it. */
saved_errno = errno;
wrs(STDERR_FILENO, "handle_done(): Caught signal ");
wri(STDERR_FILENO, signum);
wrn(STDERR_FILENO, "\n", 1);
/* Restore errno to its saved value. */
errno = saved_errno;
}
/* Helper function for installing the signal handler.
*/
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int main(void)
{
int i = 0;
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("Run\n");
printf(" kill -HUP %ld\n", (long)getpid());
printf(" kill -INT %ld\n", (long)getpid());
printf(" kill -TERM %ld\n", (long)getpid());
printf("in another terminal window, or press Ctrl-C.\n");
fflush(stdout);
while (!done) {
printf("(%d) ", i++);
fflush(stdout);
sleep(1);
}
printf("Terminated with done = %ld.\n", (long)done);
return EXIT_SUCCESS;
}
使用 eg 編譯並運行它
gcc -Wall -O2 breakme.c -o breakme
./breakme
注意,4名wr*()
函數是異步信號安全函數輸出一個字符串( wrs()
時,規定數目字符的( wrn()
或一個有符號(長)的整數( wri()
的指定的低級描述符; 在這里,標准錯誤( STDERR_FILENO
)。 您不應該將這些與<stdio.h>
函數混合在一起。
(請注意,breakme.c 僅在(某些)信號處理程序無法安裝時才使用fprintf(stderr, ..)
並立即退出(具有失敗退出狀態)。當然,我們可以使用三個wrs()
調用來代替,首先將錯誤字符串抓取到一個臨時變量中,例如const char *msg = strerror(errno);
因為wr*()
函數可能會修改errno
,但我認為走那么遠是不明智的。我相信這對程序嘗試報告確切問題,然后盡快退出。但是,我不會在程序正常運行期間使用fprintf(stderr,)
,以免弄亂標准錯誤輸出。)
特別注意install_done()
函數。 如果成功安裝handle_done
函數作為指定的信號處理程序,則返回 0,否則返回 errno。
我建議您嘗試使用該程序。 例如,將done =
行更改為done++;
, 以及while (!done)
到while (done < 3)
例如,這樣只有捕獲到的第三個信號才會導致程序退出。
最后,請注意像INT
這樣的標准 POSIX 信號在技術上並不“可靠” :無法保證它們的交付。 特別是,信號沒有排隊,因此如果您設法在第一個傳送之前發送兩個INT
信號,則只會傳送一個。 操作系統/內核盡最大努力確保傳遞信號,但開發人員應該了解技術限制。
請注意,POSIX 實時信號 -- SIGRTMIN+0
到SIGRTMAX-0
,包括; 至少有 8 個,或SIGRTMAX-SIGRTMIN+1 >= 8
-- 排隊,盡管它們也不完全可靠。 它們也支持通過sigqueue()
函數傳遞一個 int 或 void 指針的有效載荷。 您需要使用帶有SA_SIGINFO
的信號處理程序來捕獲有效負載,或使用sigwaitinfo()
/ sigtimedwait()
來捕獲循環中的阻塞信號。 我相信這將是一個有趣的練習,雖然很容易,但修改上述程序以檢測和顯示有效載荷,以及第二個程序(由用戶同時單獨運行)發送信號和整數作為有效載荷到指定的過程; 我建議將該程序編寫為僅采用三個參數(進程 ID、信號編號和有效負載整數)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.