- 一、信号
- 1. 什麼是信号?
- 2. 哪些情況會引發信号?
- 3.系統到底有哪些信号呢?
- 4.信号處理的三種方式
- 5.可靠信号&&不可靠信号
- 6.實時信号&&非實時信号
- 7.背景程序&&前台程序
- 二、程序間的各個信号詳解
- 三、操作信号
- 1.注冊信号
- (1)作用
- (2)函數功能分析——signal()函數
- (3)執行個體驗證
- (4)另一個可以擷取并處理信号的函數——pause()
- 2.發送信号
- (1)指令形式
- (2)函數形式
- (3)執行個體驗證
- (4)補充另外兩個發送信号的函數
- 1.注冊信号
- 三、經典信号
- 1.SIGALRM信号
- (1) alarm()函數文法
- (2)執行個體驗證
- (3)alarm函數的逾時處理機制
- (4)alarm的定時處理
- 4.1)用于定時的函數:setitiimer()
- 1.SIGALRM信号
一、信号
1. 什麼是信号?
1.信号是linux系統為了響應某些狀況而産生的事件。程序收到信号應該采取相應的操作。
2.信号是異步事件,當信号到達,儲存目前程序的執行環境,轉去執行信号處理函數,
當信号處理函數執行函數完畢,恢複現場,繼續執行
2. 哪些情況會引發信号?
1)鍵盤事件 ctrl+c ctrl+\
2) 非法記憶體
3)硬體故障
4)環境切換
3.系統到底有哪些信号呢?
——用kill -l 檢視
1)關閉終端 2)ctrl +c 3)ctrl\ ——以call的形式推出 4)非法指令
5)陷入核心 6)abort程序終止 7)總線錯誤 8)浮點數溢出
9)殺死程序 10) 使用者自己定義 11)段錯誤 12)由使用者自己定義
13)管道破裂 14)鬧鐘 15)缺少某個程序 17)子程序死亡
18)繼續 19)暫停 23)緊急資料 29)用來做異步IO
32) 33)拿去做多線程
4.信号處理的三種方式
1.預設處理方式:man 7 signal
2.忽略:信号來了,不進行處理,裝作沒看見(忽視、丢掉)
——-
SIGKILL SIGSTOP 這兩個信号是不能被忽略的
3.捕獲并處理:信号來了,執行我們自己寫的代碼
——-
SIGKILL SIGSTOP 這兩個信号是不能被捕獲的
5.可靠信号&&不可靠信号
-
不可靠信号:(1-31号信号)
——原本的Unix拿下來的信号都是不可靠信号(不支援排隊)
linux的信号繼承自早期的Unix信号,Unix信号的缺陷:
1.信号處理函數執行完畢,信号恢複成預設的處理方式(linux已經改進)
2.(當同時來多個信号時)會出現信号丢失,信号不排隊
-
不會出現信号丢失,支援排隊,信号處理函數執行完畢,不會恢複成預設的處理方式。
可靠信号:(34-64号信号)
6.實時信号&&非實時信号
-
實時信号:
可靠信号,沒有快慢一說
-
不可靠信号
非實時信号:
7.背景程序&&前台程序
背景程序:
-
jobs
檢視有哪些背景程序
-
fg%id
将背景程序調到前台程序(id是作業号,不是程序号)
-
ctrl+z
将前台程序轉到背景程序
-
隻能發送背景程序
ctrl+c
前台程序:
./a.out &——直接以背景方式啟動
fg%1(1——作業編号,即第一個程序)
二、程序間的各個信号詳解
) SIGHUP
本信号在使用者終端連接配接(正常或非正常)結束時發出, 通常是在終端的控制程序結束時, 通知同一session内的各個作業, 這時它們與控制終端不再關聯。
登入Linux時,系統會配置設定給登入使用者一個終端(Session)。在這個終端運作的所有程式,包括前台程序組和背景程序組,一般都屬于這個 Session。當使用者退出Linux登入時,前台程序組和背景有對終端輸出的程序将會收到SIGHUP信号。這個信号的預設操作為終止程序,是以前台程序組和背景有終端輸出的程序就會中止。不過可以捕獲這個信号,比如wget能捕獲SIGHUP信号,并忽略它,這樣就算退出了Linux登入,wget也能繼續下載下傳。
此外,對于與終端脫離關系的守護程序,這個信号用于通知它重新讀取配置檔案。
) SIGINT
程式終止(interrupt)信号, 在使用者鍵入INTR字元(通常是Ctrl-C)時發出,用于通知前台程序組終止程序。
) SIGQUIT
和SIGINT類似, 但由QUIT字元(通常是Ctrl-\)來控制. 程序在因收到SIGQUIT退出時會産生core檔案, 在這個意義上類似于一個程式錯誤信号。
) SIGILL
執行了非法指令. 通常是因為可執行檔案本身出現錯誤, 或者試圖執行資料段. 堆棧溢出時也有可能産生這個信号。
) SIGTRAP
由斷點指令或其它trap指令産生. 由debugger使用。
) SIGABRT
調用abort函數生成的信号。
) SIGBUS
非法位址, 包括記憶體位址對齊(alignment)出錯。比如通路一個四個字長的整數, 但其位址不是的倍數。它與SIGSEGV的差別在于後者是由于對合法存儲位址的非法通路觸發的(如通路不屬于自己存儲空間或隻讀存儲空間)。
) SIGFPE
在發生緻命的算術運算錯誤時發出. 不僅包括浮點運算錯誤, 還包括溢出及除數為等其它所有的算術的錯誤。
) SIGKILL
用來立即結束程式的運作. 本信号不能被阻塞、處理和忽略。如果管理者發現某個程序終止不了,可嘗試發送這個信号。
) SIGUSR1
留給使用者使用
) SIGSEGV
試圖通路未配置設定給自己的記憶體, 或試圖往沒有寫權限的記憶體位址寫資料.
) SIGUSR2
留給使用者使用
) SIGPIPE
管道破裂。這個信号通常在程序間通信産生,比如采用FIFO(管道)通信的兩個程序,讀管道沒打開或者意外終止就往管道寫,寫程序會收到SIGPIPE信号。此外用Socket通信的兩個程序,寫程序在寫Socket的時候,讀程序已經終止。
) SIGALRM
時鐘定時信号, 計算的是實際的時間或時鐘時間. alarm函數使用該信号.
) SIGTERM
程式結束(terminate)信号, 與SIGKILL不同的是該信号可以被阻塞和處理。通常用來要求程式自己正常退出,shell指令kill預設産生這個信号。如果程序終止不了,我們才會嘗試SIGKILL。
) SIGCHLD
子程序結束時, 父程序會收到這個信号。
如果父程序沒有處理這個信号,也沒有等待(wait)子程序,子程序雖然終止,但是還會在核心程序表中占有表項,這時的子程序稱為僵屍程序。這種情況我們應該避免(父程序或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子程序,或者父程序先終止,這時子程序的終止自動由init程序來接管)。
) SIGCONT
讓一個停止(stopped)的程序繼續執行. 本信号不能被阻塞. 可以用一個handler來讓程式在由stopped狀态變為繼續執行時完成特定的工作. 例如, 重新顯示提示符
) SIGSTOP
停止(stopped)程序的執行. 注意它和terminate以及interrupt的差別:該程序還未結束, 隻是暫停執行. 本信号不能被阻塞, 處理或忽略.
) SIGTSTP
停止程序的運作, 但該信号可以被處理和忽略. 使用者鍵入SUSP字元時(通常是Ctrl-Z)發出這個信号
) SIGTTIN
當背景作業要從使用者終端讀資料時, 該作業中的所有程序會收到SIGTTIN信号. 預設時這些程序會停止執行.
) SIGTTOU
類似于SIGTTIN, 但在寫終端(或修改終端模式)時收到.
) SIGURG
有”緊急”資料或out-of-band資料到達socket時産生.
) SIGXCPU
超過CPU時間資源限制. 這個限制可以由getrlimit/setrlimit來讀取/改變。
) SIGXFSZ
當程序企圖擴大檔案以至于超過檔案大小資源限制。
) SIGVTALRM
虛拟時鐘信号. 類似于SIGALRM, 但是計算的是該程序占用的CPU時間.
) SIGPROF
類似于SIGALRM/SIGVTALRM, 但包括該程序用的CPU時間以及系統調用的時間.
) SIGWINCH
視窗大小改變時發出.
) SIGIO
檔案描述符準備就緒, 可以開始進行輸入/輸出操作.
) SIGPWR
Power failure
) SIGSYS
非法的系統調用。
在以上列出的信号中,程式不可捕獲、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢複至預設動作的信号有:SIGILL,SIGTRAP
預設會導緻程序流産的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
預設會導緻程序退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
預設會導緻程序停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
預設程序忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
三、操作信号
1.注冊信号
(1)作用
注冊信号實際上是對信号進行三種基本處理操作,用于告訴目前程序接受到信号後應該進行什麼處理操作。
(2)函數功能分析——signal()函數
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, //要注冊的信号
sighandler_t handler);//信号執行函數
//第一個參數 signum:說明所要處理的信号類型,它可以取除了SIGKILL和SIGSTOP外的任何一種信号。
//第二個參數 handler:描述了與信号關聯的動作,它可以取以下三種值:
SIG_IGN:表示忽略該信号。 #define SIG_IGN ((sighandler_t)1)
SIG_DFL:表示恢複對信号的系統預設處理。不寫此處理函數預設也是執行系統預設操作。#define SIG_IGN ((sighandler_t)0)
sighandler_t類型的函數指針:執行自己寫的代碼。
//傳回錯誤: SIG_ERR
SIG_ERR #define SIG_IGN((sighandler_t)-1)
//傳回值:
成功傳回原本的信号處理函數指針;
失敗傳回SIGERR; SIGERR的宏為 #define SIG_IGN ((sighandler_t)-1)
(3)執行個體驗證
//測試SIG_DFL
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
int main()
{
int i=;
signal(SIGINT,SIG_DFL);//SIG_INT是ctlr c發起的
//SIG_DFL表示采取預設處理方式處理接受到的信号
//把這句代碼去掉,執行結果一樣
for(i=;i<;++i)
{
printf("下午好:%d\n",i);
sleep();
}
return ;
}
//測試SIG_IGN
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
int main()
{
int i=;
signal(SIGINT,SIG_IGN);//SIGINT 是ctrl c發起的信号
//SIG_IGN表示忽略接受到的信号
for(i=;i<;++i)
{
printf("我最瘦:%d\n",i);
sleep();
}
return ;
}
//測試sighandler_t
#include<unistd.h>
#include<signal.h>
void handler(int n)//自定義一個函數
{
printf("算了吧,還不減肥呀!\n");
exit();
}
int main()
{
int i=;
signal(SIGINT,handler);
while()
{
printf("想吃巧克力蛋糕!!!\n");
sleep();
}
return ;
}
(4)另一個可以擷取并處理信号的函數——pause()
- 函數文法:
- 函數功能:暫停程序,将目前程序設為就緒态,讓出CPU,直到收到任意一個信号終止,并且處理完該信号之後,直接執行pause()下面的語句。
- 參數解釋:無需傳參
- 執行個體驗證:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
//該執行個體可以了解為實作了sleep()函數的功能
int main()
{
int ret=alarm();//設定一個定時器
pause();//捕捉定時器信号
//目前程序暫停,直到捕獲到任意一個信号,處理完後直接執行printf語句
printf("I have been waken up\n",ret);
}
這個函數中的printf是不會執行的,因為定時器發送的SIGARAM的預設處理是終止程式,是以程式列印之前程式已經結束了,與sleep不同的是sleep是不會退出的。
2.發送信号
(1)指令形式
- kill -信号值 pid
(2)函數形式
//頭檔案
#include <sys/types.h>
#include <signal.h>
int kill(int pid, int signum);
//第一個參數
pid>:發送給pid程序,pid是信号欲送往的程序的辨別。
pid=:調用者所在程序組的任何一個程序,信号将送往所有與調用kill()的那個程序屬同一個使用組的程序。
pid=-:信号将送往所有調用程序有權給其發送信号的程序,除了程序(init)。
pid<-:|pid|程序組的所有程序,信号将送往所有調用程序有權給其發送信号的程序,除了程序(init)。
//第二個參數
signum:準備發送的信号代碼,-`假如其值為,則沒有任何信号發出`,但是系統會執行錯誤檢查,通常會-`利用sig值為來檢驗某個程序是否仍在執行。`
//傳回值:
成功執行時,傳回。
失敗傳回-,errno 被設為以下的某個值
EINVAL:指定的信号碼無效(參數 sig 不合法)
EPERM;權限不夠無法傳送信号給指定程序
ESRCH:參數 pid 所指定的程序或程序組不存在
(3)執行個體驗證
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void handler(int n)//自定義函數,用于驗證能接收到信号
{
printf("在的呀,我收到信号啦\n");
}
int main()
{
int i=;
signal(SIGUSR1,handler);//父程序擷取信号SIGUSR1
//SIGUSR1:用于自定義信号,常用于發送和接收
pid_t pid=fork();
if(pid==)//子程序
{
sleep();
kill(getppid(),SIGUSR1);//将信号SIGUSR1發送給父程序
exit();
}
else
{
while()
{
printf("叮咚叮咚你在嘛\n");
sleep();
}
}
return ;
}
(4)補充另外兩個發送信号的函數
1.給自己發送信号:-
int raise (int signum)
傳回值:成功傳回0,失敗傳回-1
2.給程序組發送信号:-
int killpg(int gid,int signum)
//注:gid是目前程序組的id
//傳回值-1,并把error值設為EINTR
三、經典信号
1.SIGALRM信号
(1) alarm()函數文法
alarm(0) 清除鬧鐘
alarm(n) 等n秒後向該程序發送信号——報逾時
(2)執行個體驗證
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void handler(int s){
printf("逾時\n");
exit();
}
int main(void)
{
char buf[]={};
printf("輸入名字:");
signal(SIGALRM,handler);//捕獲到SIGALRM信号執行後執行handler函數
alarm();//如果3seconds後,SIGALRM信号沒有被清除,則擷取SIG信号
scanf("%s",buf);
alarm();//清除SIGALRM信号
printf("名字為:%s\n",buf);
for( ; ;)//用于驗證alarm(0)确實清除了SIGALRM信号
{
printf("叮咚~\n");
sleep();
}
}
(3)alarm函數的逾時處理機制
——以簡單的考試計數程式驗證
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
int Right =;//做對的題數
int Wrong =;//做錯的題數
void handler(int s)//用于驗證逾時
{
printf("time is over\n");
printf("Right=%d,Wrong=%d\n",Right,Wrong);
exit();
}
int main(void)
{
int i=;
signal(SIGALRM,handler);//擷取SIGALRM信号,執行handler函數
alarm();//設定倒計時為20
srand(getpid());//随機數種子
for(i=;i<;i++)//出10道10以内的加法題
{
int left=rand()%;
int right=rand()%;
printf("%d+%d=",left,right);
int ret;
scanf("%d",&ret);
if(left + right == ret)
{
Right++;
}
else
{
Wrong++;
}
}
printf("做完了\n");
printf("Right=%d,Wrong=%d\n",Right,Wrong);
}
(4)alarm的定時處理
4.1)用于定時的函數:setitiimer()
1. 函數原型:
#include<sys/time.h>
int setitimer(int which,
const struct itimerval* new_value,
struct itimerval* old_value);
2.函數功能解釋:
設定一個定時器,從第n秒開始啟動定時器,每隔m秒發送SIGALRM信号(這裡的 n 和 m 都是自己設定的)
3.傳回值:
失敗:傳回-1 ; 成功:傳回0
4.參數解釋:
一般寫成
which:
,即真實的桌面時間
ITIMER_REAL
一個結構體指針,結構體内部存儲着“啟動時間”和“間隔時間”,稍後詳解
new_value:
同樣的結構體指針,一般寫成
old_value:
NULL
5.對struct itimerval* new_value 的詳解
**結構體内部的資訊
struct itimerval
{
struct timeval it_interval;//以後每一次執行的間隔時間
struct timeval it_value;//第一次執行的時間
};
struct timeval//時間的精确度
{
long tv_sec;//時間的秒部分
long tv_usec;//時間的微秒部分
}
**執行個體說明該結構體的作用
struct itimerval it;
//上句代碼的作用是定義一個該結構體變量it,it包括兩部分資訊:啟動時間和間隔時間
it.it_value.tv.sec=;
it.it_value.tv.usec=;
//上兩句代碼的作用是将it的啟動時間設為:真實的桌面時間+0秒+1微秒
it.it_interval.tv.sec=;
it.it_interval.tv.usec=;
//上兩句代碼的作用是将it的間隔時間設定為1秒0微秒
6.執行個體分析:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/time.h>
printf("收到SIGALRM信号了\n");
}
{
struct itimerval it;
//上句代碼的作用是定義一個該結構體變量it,it包括兩部分資訊
:啟動時間 和 間隔時間
it.it_value.tv_sec = ;
it.it_value.tv_usec = ;
//上兩句代碼的作用是将it的啟動時間設為:真實時間+0秒1微秒
it.it_interval.tv_sec = ;
it.it_interval.tv_usec = ;
//上兩句代碼的作用是将it的間隔時間設為:1秒0微秒
setitimer(ITIMER_REAL,&it,NULL);
//上面的定時器内部資訊設定完成後,通過setitimer函數啟動該>定時器
while()//無限循環,用于驗證定時器确實是每隔一段時間就發出
一個SIGALAM信号
;
}