信號(hào)是指示事件發(fā)生的進(jìn)程的通知。 信號(hào)也被稱為軟件中斷,不可預(yù)測(cè)知道它的發(fā)生,因此它也被稱為異步事件。
信號(hào)可以用數(shù)字或名稱來指定,通常信號(hào)名稱以SIG開頭。 可用的信號(hào)可以用命令kill -l
(l列出信號(hào)名稱)來檢查,如下所示 -
無論何時(shí)發(fā)出信號(hào)(以編程方式或系統(tǒng)產(chǎn)生的信號(hào)),都會(huì)執(zhí)行默認(rèn)操作。 如果您不想執(zhí)行默認(rèn)操作但希望在接收信號(hào)時(shí)執(zhí)行自己的操作。 可以處理信號(hào),但不能處理所有的信號(hào)。 如果你想忽略信號(hào),這是可能的嗎? 這是可以忽略信號(hào)。 忽略信號(hào)意味著既不執(zhí)行默認(rèn)操作也不處理信號(hào)。 可以忽略或處理幾乎所有的信號(hào)。 SIGSTOP
和SIGKILL
不能被忽略或處理/捕獲的信號(hào)。
總之,對(duì)信號(hào)執(zhí)行的操作如下 -
正如所討論的,信號(hào)可以被處理,改變默認(rèn)行為的執(zhí)行。 信號(hào)處理可以通過系統(tǒng)調(diào)用,signal()
和sigaction()
兩種方式完成。
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
系統(tǒng)調(diào)用signal()
將在符號(hào)中提到的信號(hào)產(chǎn)生時(shí)調(diào)用注冊(cè)的處理程序。 處理程序可以是SIG_IGN(忽略信號(hào)),SIG_DFL(將信號(hào)設(shè)置回默認(rèn)機(jī)制)或用戶定義的信號(hào)處理程序或函數(shù)地址之一。
這個(gè)系統(tǒng)調(diào)用成功返回一個(gè)函數(shù)的地址,該函數(shù)接受一個(gè)整數(shù)參數(shù)并且沒有返回值。 這個(gè)調(diào)用在出錯(cuò)的時(shí)候返回SIG_ERR
。
雖然signal()
可以調(diào)用用戶注冊(cè)的各自的信號(hào)處理程序,但是不可能進(jìn)行微調(diào),例如掩蔽應(yīng)該被阻塞的信號(hào),修改信號(hào)的行為以及其他功能。 這可以使用sigaction()
系統(tǒng)調(diào)用。
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
這個(gè)系統(tǒng)調(diào)用用于檢查或改變信號(hào)動(dòng)作。 如果行為不為空,則從行為安裝信號(hào)的新行為。 如果oldact
不為null
,則以前的操作將保存在oldact
中。
sigaction
結(jié)構(gòu)包含以下字段 -
字段1 - 處理程序在sa_handler
或sa_sigaction
中提到。
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sa_handler
的處理程序指定要根據(jù)signum
和SIG_DFL
指示默認(rèn)操作執(zhí)行的操作,或者SIG_IGN
忽略指向信號(hào)處理函數(shù)的信號(hào)或指針。sa_sigaction
的處理程序?qū)⑿盘?hào)編號(hào)指定為第一個(gè)參數(shù),將指向siginfo_t
結(jié)構(gòu)體的指針指定為第二個(gè)參數(shù),并指定用戶上下文(有關(guān)詳細(xì)信息,請(qǐng)參閱getcontext()
或setcontext()
)作為第三個(gè)參數(shù)。
siginfo_t
結(jié)構(gòu)包含信號(hào)信息,例如要傳遞的信號(hào)數(shù)量,信號(hào)值,進(jìn)程ID,發(fā)送進(jìn)程的真實(shí)用戶ID等。
字段2 - 要阻止的信號(hào)集。
int sa_mask;
這個(gè)變量指定了信號(hào)處理程序執(zhí)行期間應(yīng)該被阻塞的信號(hào)掩碼。
字段3 - 特殊標(biāo)志。
int sa_flags;
該字段指定一組修改信號(hào)行為的標(biāo)志。
字段4 - 恢復(fù)處理程序。
void (*sa_restorer) (void);
這個(gè)系統(tǒng)調(diào)用在成功時(shí)返回0
,在失敗的情況下為-1
。
讓我們考慮一些示例程序。
首先,從一個(gè)生成異常的示例程序開始。 在這個(gè)程序中,試圖執(zhí)行除零運(yùn)算,這會(huì)使系統(tǒng)產(chǎn)生一個(gè)異常。
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
編譯和運(yùn)行上面示例代碼,得到以下結(jié)果 -
Floating point exception (core dumped)
因此,當(dāng)試圖執(zhí)行一個(gè)算術(shù)運(yùn)算時(shí),系統(tǒng)已經(jīng)產(chǎn)生了一個(gè)浮點(diǎn)異常,核心轉(zhuǎn)儲(chǔ)是信號(hào)的默認(rèn)操作。
現(xiàn)在,修改代碼以使用signal()
系統(tǒng)調(diào)用來處理這個(gè)特定的信號(hào)。
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
執(zhí)行上面示例代碼,得到以下結(jié)果 -
Received SIGFPE, Divide by Zero Exception
正如所討論的那樣,信號(hào)是由系統(tǒng)產(chǎn)生的(在執(zhí)行某些操作(例如除以零等)時(shí)),或者用戶也可以以編程方式產(chǎn)生信號(hào)。 如果要以編程方式生成信號(hào),請(qǐng)使用庫函數(shù)raise()
。
現(xiàn)在,另外一個(gè)程序來演示處理和忽略信號(hào)。
假設(shè)用raise()
發(fā)出了一個(gè)信號(hào),那么會(huì)發(fā)生什么? 發(fā)出信號(hào)后,當(dāng)前進(jìn)程的執(zhí)行停止。 那么停止的過程會(huì)發(fā)生什么? 可以有兩種情況 - 首先,在需要時(shí)繼續(xù)執(zhí)行。 其次,終止(用kill命令)這個(gè)進(jìn)程。
要繼續(xù)執(zhí)行已停止的進(jìn)程,請(qǐng)將SIGCONT發(fā)送到該特定進(jìn)程。 也可以發(fā)出fg(前景)或bg(后臺(tái))命令來繼續(xù)執(zhí)行。 在這里,這些命令只會(huì)重新開始執(zhí)行最后一個(gè)進(jìn)程。 如果多個(gè)進(jìn)程停止,則只有最后一個(gè)進(jìn)程被恢復(fù)。 如果想恢復(fù)先前停止的過程,請(qǐng)使用fg/bg
和作業(yè)編號(hào)恢復(fù)作業(yè)。
下面的程序用于使用raise()
函數(shù)來提升信號(hào)SIGSTOP。 信號(hào)SIGSTOP也可以由用戶按下CTRL + Z(Ctrl + Z)鍵生成。 發(fā)出此信號(hào)后,程序?qū)⑼V箞?zhí)行。 發(fā)送信號(hào)(SIGCONT)繼續(xù)執(zhí)行。
在下面的例子中,使用命令fg
重新開始停止的進(jìn)程。
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
執(zhí)行上面示例代碼,得到以下結(jié)果 -
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
現(xiàn)在,增強(qiáng)以前的程序,通過從另一個(gè)終端發(fā)出SIGCONT來繼續(xù)執(zhí)行停止的進(jìn)程。
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
執(zhí)行上面示例代碼,得到以下結(jié)果 -
Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out
在另一個(gè)終端執(zhí)行 -
kill -SIGCONT 30379
到目前為止,我們已經(jīng)看到了處理系統(tǒng)產(chǎn)生的信號(hào)的程序。 現(xiàn)在讓我們看看通過程序產(chǎn)生的信號(hào)(使用raise()
函數(shù)或者通過kill命令)。 該程序產(chǎn)生信號(hào)SIGTSTP(終端停止),其默認(rèn)動(dòng)作是停止執(zhí)行。 但是,由于現(xiàn)在正在處理信號(hào),而不是默認(rèn)的動(dòng)作,它會(huì)來到定義的處理程序。 在這種情況下,只是打印消息并退出。
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
執(zhí)行上面示例代碼,得到以下結(jié)果 -
Testing SIGTSTP
Received SIGTSTP
已經(jīng)看到了執(zhí)行默認(rèn)操作或處理信號(hào)的情況。 現(xiàn)在是時(shí)候忽略這個(gè)信號(hào)了。 在這個(gè)示例程序中,通過SIG_IGN注冊(cè)信號(hào)SIGTSTP忽略,然后發(fā)送信號(hào)SIGTSTP(終端停止)。 當(dāng)信號(hào)SIGTSTP正在被生成,將被忽略。參考以下代碼 -
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
執(zhí)行上面示例代碼,得到以下結(jié)果 -
Testing SIGTSTP
Signal SIGTSTP is ignored
到目前為止,我們已經(jīng)觀察到,一個(gè)信號(hào)處理程序用來處理一個(gè)信號(hào)。 那么一個(gè)信號(hào)可以處理多個(gè)信號(hào)嗎? 答案是肯定的 讓我們通過一個(gè)程序來實(shí)現(xiàn)。
以下程序執(zhí)行以下操作 -
第1步 - 注冊(cè)一個(gè)處理程序(handleSignals)來捕獲或處理信號(hào)SIGINT(CTRL + C)或SIGQUIT(CTRL + )
第2步 - 如果用戶產(chǎn)生信號(hào)SIGQUIT(或者通過kill命令或者用CTRL + \鍵盤控制),處理程序簡(jiǎn)單地將消息打印為返回。
第3步 - 如果用戶第一次生成信號(hào)SIGINT(通過kill命令或者使用CTRL + C的鍵盤控制),則修改信號(hào)從下次開始執(zhí)行默認(rèn)操作(使用SIG_DFL)。
第4步 - 如果用戶第二次生成信號(hào)SIGINT,則執(zhí)行默認(rèn)操作,即程序終止。
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
編譯和執(zhí)行上面示例代碼,得到以下結(jié)果 -
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated
在另一個(gè)終端中執(zhí)行 -
kill 71
第二種方法 -
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
我們知道要處理一個(gè)信號(hào),有兩個(gè)系統(tǒng)調(diào)用,即signal()
或sigaction()
。 直到現(xiàn)在我們已經(jīng)看到了signal()
系統(tǒng)調(diào)用,現(xiàn)在是時(shí)候進(jìn)行sigaction()
系統(tǒng)調(diào)用了。 讓我們修改上面的程序來執(zhí)行使用sigaction()
,如下所示 -
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
讓我們編譯和執(zhí)行上面的代碼。 在執(zhí)行過程中,按CTRL + C兩次,其余的檢查/方式(如上)來嘗試執(zhí)行這個(gè)程序。
執(zhí)行上面示例代碼,得到以下結(jié)果 -
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C