Linux中行程的信號
我們在Linux的shell下啟動一個前臺行程,然后按下ctrl+c使行程中斷,那有沒有想過它的底層原理是什么?
行程信號
- Linux中行程的信號
- 一、行程信號是什么?
- 二、信號的生命周期
- 1.信號產生
- 2.信號在行程中注冊
- 3.信號注銷
- 4.信號處理
- 信號的阻塞
一、行程信號是什么?
行程信號是一種“軟體中斷”,一種事件通知機制,通知行程發生了某個事件,打斷行程當前操作,去處理這個事件,
查看信號種類:kill -l

每個信號表示一個事件,總共有62種信號,1-31號信號是借鑒Unix作業系統的非可靠信號(可能會信號丟失),34-64號是可靠信號,
在路徑: /usr/include/bits/signum.h,查看這個頭檔案,這些信號都被定義為宏,
二、信號的生命周期
產生——注冊——注銷——處理
1.信號產生
1.硬體產生: ctrl+c:發送SIGINT中斷信號;
ctrl+\ :發送SOGQUIT退出信號;

ctrl+z:發送SIGSTOP停止信號
int main(int argc, char *argv[])
{
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}
該行程處于“T”狀態,行程由前臺行程變為后臺行程,
jobs:查看后臺停止作業;
fg 作業序號:將該后臺停止作業轉換到前臺,

2.軟體產生:
kill(命令):kill -signum pid 殺死一個行程,給行程發送一個信號-默認是終止信號(例如kill -9 就是發送SIGKILL信號),如果不指定信號,則發送默認信號SIGTERM;
kill函式(系統呼叫):int kill(pid_t pid, int sig);
引數解釋:pid:指定要將信號發送給哪個行程;
sig:指定要發送的信號;
回傳值:成功回傳0,失敗回傳-1.
int main(int argc, char *argv[])
{
kill(getpid(),SIGINT);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}

運行程式直接中斷,

int raise(int sig):給呼叫行程自身發送指定信號,
unsigned int alarm(unsigned int seconds):設定一個定時器,多少seconds秒后,給行程發送一個SIGALRM信號(該信號默認處理方式為退出行程),
int main(int argc, char *argv[])
{
alarm(3);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}

void abort(void):給行程發送一個SIGABRT信號,abort函式使當前行程接收到信號而例外終止,
2.信號在行程中注冊
在行程的pcb中有個未決信號集合(pending)-還沒有被處理的信號的集合——位圖,還有一個struct sigqueue專門存放信號資訊節點的佇列(雙向鏈表),
注冊就是在未決信號集合中標記信號將0置為1,然后給雙向鏈表中添加信號的資訊節點,
非可靠信號:若信號已經注冊,則不作任何操作;
可靠信號:無論信號是否注冊,都會進行注冊(置1加添加節點),

3.信號注銷
洗掉在行程pcb中的信號資訊節點,重置位圖為0,
非可靠信號:洗掉資訊節點,直接位圖置0;
可靠信號:洗掉資訊節點之后,確定沒有相同節點才會重置位圖,
4.信號處理
執行信號的處理回呼函式(sigaction-handler),

如何修改信號的處理流程,暨修改處理函式?
1.sighandler_t signal(int signum, sighandler_t handler);
引數解釋:
sigunm:信號值;
handler:信號要新指定的處理方式;
handler:SIG_DFL-默認;SIG_IGN-忽略;自定義函式,
自定義函式的型別:typedef void (*sighandler_t)(int);
回傳值:成功回傳信號原來的處理方式,失敗回傳-1(SIG_ERR),
舉例忽略SIGINT,暨忽略ctrl+c:
int main(int argc, char *argv[])
{
signal(SIGINT,SIG_IGN);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}

自定義處理回呼函式:
把SIGINT的處理方式改為列印信號值,
void sigcb(int signo)
{
printf("recv signo : %d\n", signo);
}
int main(int argc, char *argv[])
{
signal(SIGINT,sigcb);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}
暨運行程式后按一次ctrl+c就會列印一次2.

注意:9號信號和19號信號不能被修改處理操作,
自定義處理方式的信號捕捉流程:

信號的處理是一種異步操作,
如何使程式運行從用戶態切換到內核態:
中斷、例外、系統呼叫
信號的阻塞
阻塞一個信號表示收到這個信號之后暫時不去處理,知道解除阻塞之后進行處理,
行程pcb中有一個信號阻塞集合,在這個集合中標記那個信號則阻塞哪個信號,

int sigprocmask(int how, const sigset_t * set, sigset_t * oldset);
引數解釋:
how:要對信號阻塞集合進行的操作型別;
oldset:保存原有集合;
set:新集合;
①SIG_BLOCK:將set集合中的信號添加到阻塞集合中;block |= set;(block:101,set:010;——111)
②SIG_UNBLOCK:從阻塞集合中移除set集合中的信號;
③SIG_SETMASK:將set集合中的信號設定為阻塞集合的信號;block = set,覆寫式設定,
回傳值:成功回傳0,失敗回傳-1.
#include <signal.h>
int sigemptyset(sigset_t *set);清空set集合
int sigfillset(sigset_t *set);將所有信號添加到set集合中
int sigaddset(sigset_t *set, int signum);將指定信號添加到集合中
int sigdelset(sigset_t *set, int signum);從集合中移除指定信號
int sigismember(const sigset_t *set, int signum);判斷信號是否在集合中
舉個栗子:自定義修改兩種信號(可靠和非可靠)signal, 先阻塞信號sigprocmask SIG_BLOCK,再使行程暫停getchar,暫停期間給行程發送信號,讓行程繼續運行,解除信號阻塞sigprocmask SIG_UNBLOCK,
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void sigcb(int signo)
{
printf("recv sino:%d\n", signo);
}
int main(int argc, char* argv[])
{
signal(SIGINT, sigcb);//非可靠信號
signal(40,sigcb);//可靠信號
sigset_t set;
sigemptyset(&set);
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set,NULL);
printf("回車解除暫停\n");
getchar();
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1)
{
sleep(1);
}
return 0;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/280659.html
標籤:其他
上一篇:Hive千億級資料傾斜解決方案
