1. 程式人生 > >7.6-UC-第六課:信號處理

7.6-UC-第六課:信號處理

del 首進程 lse 捕獲 signed 函數 設備 null 同進程

================第六課 信號處理================
一、基本概念------------
1. 中斷~~~~~~~
中止(註意不是終止)當前正在執行的程序,轉而執行其它任務。
硬件中斷:來自硬件設備的中斷。軟件中斷:來自其它程序的中斷。
2. 信號是一種軟件中斷~~~~~~~~~~~~~~~~~~~~~
信號提供了一種以異步方式執行任務的機制。
3. 常見信號~~~~~~~~~~~
SIGHUP(1):連接斷開信號如果終端接口檢測一個連接斷開,則將此信號發送給與該終端相關的控制進程(會話首進程)。默認動作:終止。
SIGINT(2):終端中斷符信號用戶按中斷鍵(Ctrl+C),產生此信號,並送至前臺進程組的所有進程。默認動作:終止。
SIGQUIT(3):終端退出符信號用戶按退出鍵(Ctrl+\),產生此信號,並送至前臺進程組的所有進程。默認動作:終止+core。
SIGILL(4):非法硬件指令信號進程執行了一條非法硬件指令。默認動作:終止+core。
SIGTRAP(5):硬件故障信號指示一個實現定義的硬件故障。常用於調試。默認動作:終止+core。
SIGABRT(6):異常終止信號調用abort函數,產生此信號。默認動作:終止+core。
SIGBUS(7):總線錯誤信號指示一個實現定義的硬件故障。常用於內存故障。默認動作:終止+core。
SIGFPE(8):算術異常信號表示一個算術運算異常,例如除以0、浮點溢出等。默認動作:終止+core。
SIGKILL(9):終止信號不能被捕獲或忽略。常用於殺死進程。默認動作:終止。
SIGUSR1(10):用戶定義信號用戶定義信號,用於應用程序。默認動作:終止。
SIGSEGV(11):段錯誤信號試圖訪問未分配的內存,或向沒有寫權限的內存寫入數據。默認動作:終止+core。
SIGUSR2(12):用戶定義信號用戶定義信號,用於應用程序。默認動作:終止。
SIGPIPE(13):管道異常信號寫管道時讀進程已終止,或寫SOCK_STREAM類型套接字時連接已斷開,均產生此信號。默認動作:終止。
SIGALRM(14):鬧鐘信號以alarm函數設置的計時器到期,或以setitimer函數設置的間隔時間到期,均產生此信號。默認動作:終止。
SIGTERM(15):終止信號由kill命令發送的系統默認終止信號。默認動作:終止。
SIGSTKFLT(16):數協器棧故障信號表示數學協處理器發生棧故障。默認動作:終止。
SIGCHLD(17):子進程狀態改變信號在一個進程終止或停止時,將此信號發送給其父進程。默認動作:忽略。
SIGCONT(18):使停止的進程繼續向處於停止狀態的進程發送此信號,令其繼續運行。默認動作:繼續/忽略。
SIGSTOP(19):停止信號不能被捕獲或忽略。停止一個進程。默認動作:停止進程。
SIGTSTP(20):終端停止符信號。用戶按停止鍵(Ctrl+Z),產生此信號,並送至前臺進程組的所有進程。默認動作:停止進程。
SIGTTIN(21):後臺讀控制終端信號後臺進程組中的進程試圖讀其控制終端,產生此信號。默認動作:停止。
SIGTTOU(22):後臺寫控制終端信號後臺進程組中的進程試圖寫其控制終端,產生此信號。默認動作:停止。
SIGURG(23):緊急情況信號有緊急情況發生,或從網絡上接收到帶外數據,產生此信號。默認動作:忽略。
SIGXCPU(24):超過CPU限制信號進程超過了其軟CPU時間限制,產生此信號。默認動作:終止+core。
SIGXFSZ(25):超過文件長度限制信號進程超過了其軟文件長度限制,產生此信號。默認動作:終止+core。
SIGVTALRM(26):虛擬鬧鐘信號以setitimer函數設置的虛擬間隔時間到期,產生此信號。默認動作:終止。
SIGPROF(27):虛擬梗概鬧鐘信號以setitimer函數設置的虛擬梗概統計間隔時間到期,產生此信號。默認動作:終止。
SIGWINCH(28):終端窗口大小改變信號以ioctl函數更改窗口大小,產生此信號。默認動作:忽略。
SIGIO(29):異步I/O信號指示一個異步I/O事件。默認動作:終止。
SIGPWR(30):電源失效信號電源失效,產生此信號。默認動作:終止。
SIGSYS(31):非法系統調用異常。指示一個無效的系統調用。默認動作:終止+core。
4. 不可靠信號(非實時信號)~~~~~~~~~~~~~~~~~~~~~~~~~
1) 那些建立在早期機制上的信號被稱為“不可靠信號”。 小於SIGRTMIN(34)的信號都是不可靠信號。
2) 不支持排隊,可能會丟失。同一個信號產生多次, 進程可能只收到一次該信號。
3) 進程每次處理完這些信號後, 對相應信號的響應被自動恢復為默認動作, 除非顯示地通過signal函數重新設置一次信號處理程序。
5. 可靠信號(實時信號)~~~~~~~~~~~~~~~~~~~~~
1) 位於[SIGRTMIN(34), SIGRTMAX(64)]區間的信號都是可靠信號。
2) 支持排隊,不會丟失。
3) 無論可靠信號還是不可靠信號, 都可以通過sigqueue/sigaction函數發送/安裝, 以獲得比其早期版本kill/signal函數更可靠的使用效果。
6. 信號的來源~~~~~~~~~~~~~
1) 硬件異常:除0、無效內存訪問等。 這些異常通常被硬件(驅動)檢測到,並通知系統內核。 系統內核再向引發這些異常的進程遞送相應的信號。
2) 軟件異常:通過 kill/raise/alarm/setitimer/sigqueue 函數產生的信號。
7. 信號處理~~~~~~~~~~~
1) 忽略。
2) 終止進程。
3) 終止進程同時產生core文件。
4) 捕獲並處理。當信號發生時, 內核會調用一個事先註冊好的用戶函數(信號處理函數)。
範例:loop.c
# a.out按中斷鍵(Ctrl+C),發送SIGINT(2)終端中斷符信號。
# a.out按退出鍵(Ctrl+\),發送SIGQUIT(3)終端退出符信號。
二、signal----------
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal (int signum, sighandler_t handler);
signum - 信號碼,也可使用系統預定義的常量宏, 如SIGINT等。
handler - 信號處理函數指針或以下常量:
SIG_IGN: 忽略該信號; SIG_DFL: 默認處理。
成功返回原來的信號處理函數指針或SIG_IGN/SIG_DFL常量,失敗返回SIG_ERR。
1. 在某些Unix系統上, 通過signal函數註冊的信號處理函數只一次有效, 即內核每次調用信號處理函數前, 會將對該信號的處理自動恢復為默認方式。 為了獲得持久有效的信號處理, 可以在信號處理函數中再次調用signal函數, 重新註冊一次。
例如:
void sigint (int signum) { ... signal (SIGINT, sigint);}
int main (void) { ... signal (SIGINT, sigint); ...}
2. SIGKILL/SIGSTOP信號不能被忽略,也不能被捕獲。
3. 普通用戶只能給自己的進程發送信號, root用戶可以給任何進程發送信號。
範例:signal.c
三、子進程的信號處理--------------------
1. 子進程會繼承父進程的信號處理方式, 直到子進程調用exec函數。
範例:fork.c
2. 子進程調用exec函數後, exec函數將被父進程設置為捕獲的信號恢復至默認處理, 其余保持不變。
範例:exec.c
四、發送信號------------
1. 鍵盤~~~~~~~
Ctrl+C - SIGINT(2),終端中斷Ctrl+\ - SIGQUIT(3),終端退出Ctrl+Z - SIGTSTP(20),終端暫停
2. 錯誤~~~~~~~
除0 - SIGFPE(8),算術異常非法內存訪問 - SIGSEGV(11),段錯誤硬件故障 - SIGBUS(7),總線錯誤
3. 命令~~~~~~~
kill -信號 進程號
4. 函數~~~~~~~
1) kill
#include <signal.h>
int kill (pid_t pid, int sig);
成功返回0,失敗返回-1。
pid > 0 - 向pid進程發送sig信號。
pid = 0 - 向同進程組的所有進程發送信號。
pid = -1 - 向所有進程發送信號, 前提是調用進程有向其發送信號的權限。
pid < -1 - 向絕對值等於pid的進程組發信號。
0信號為空信號。若sig取0,則kill函數仍會執行錯誤檢查,但並不實際發送信號。這常被用來確定一個進程是否存在。向一個不存在的進程發送信號,會返回-1,且errno為ESRCH。
範例:kill.c
2) raise
#include <signal.h>
int raise (int sig);
向調用進程自身發送sig信號。成功返回0,失敗返回-1。
範例:raise.c
五、pause---------
#include <unistd.h>
int pause (void);
1. 使調用進程進入睡眠狀態, 直到有信號終止該進程或被捕獲。
2. 只有調用了信號處理函數並從中返回以後, 該函數才會返回。
3. 該函數要麽不返回(未捕獲到信號), 要麽返回-1(被信號中斷), errno為EINTR。
4. 相當於沒有時間限制的sleep函數。
範例:pause.c
六、sleep---------
#include <unistd.h>
unsigned int sleep (unsigned int seconds);
1. 使調用進程睡眠seconds秒, 除非有信號終止該進程或被捕獲。
2. 只有睡夠seconds秒, 或調用了信號處理函數並從中返回以後, 該函數才會返回。
3. 該函數要麽返回0(睡夠), 要麽返回剩余秒數(被信號中斷)。
4. 相當於有時間限制的pause函數。
範例:sleep.c
#include <unistd.h>
int usleep (useconds_t usec);
使調用進程睡眠usec微秒,除非有信號終止該進程或被捕獲。成功返回0,失敗返回-1。
七、alarm---------
#include <unistd.h>
unsigned int alarm (unsigned int seconds);
1. 使內核在seconds秒之後, 向調用進程發送SIGALRM(14)鬧鐘信號。
範例:clock.c
2. SIGALRM信號的默認處理是終止進程。
3. 若之前已設過定時且尚未超時, 則調用該函數會重新設置定時, 並返回之前定時的剩余時間。
4. seconds取0表示取消之前設過且尚未超時的定時。
範例:alarm.c
八、信號集與信號阻塞(信號屏蔽)------------------------------
1. 信號集~~~~~~~~~
1) 多個信號的集合類型: sigset_t,128個二進制位,每個位代表一個信號。
2) 相關函數
#include <signal.h>
// 將信號集set中的全部信號位置1int sigfillset (sigset_t* set);
// 將信號集set中的全部信號位清0int sigemptyset (sigset_t* set);
// 將信號集set中與signum對應的位置1int sigaddset (sigset_t* set, int signum);
// 將信號集set中與signum對應的位清0int sigdelset (sigset_t* set, int signum);
成功返回0,失敗返回-1。
// 判斷信號集set中與signum對應的位是否為1int sigismember (const sigset_t* set, int signum);
若信號集set中與signum對應的位為1,則返回1,否則返回0。
範例:sigset.c
2. 信號屏蔽~~~~~~~~~~~
1) 當信號產生時,系統內核會在其所維護的進程表中, 為特定的進程設置一個與該信號相對應的標誌位, 這個過程稱為遞送(delivery)。
2) 信號從產生到完成遞送之間存在一定的時間間隔。 處於這段時間間隔中的信號狀態,稱為未決(pending)。
3) 每個進程都有一個信號掩碼(signal mask)。 它實際上是一個信號集, 其中包括了所有需要被屏蔽的信號。
4) 可以通過sigprocmask函數, 檢測和修改調用進程的信號掩碼。 也可以通過sigpending函數, 獲取調用進程當前處於未決狀態的信號集。
5) 當進程執行諸如更新數據庫等敏感任務時, 可能不希望被某些信號中斷。 這時可以暫時屏蔽(註意不是忽略)這些信號, 使其滯留在未決狀態。 待任務完成以後,再回過頭來處理這些信號。
6) 在信號處理函數的執行過程中, 這個正在被處理的信號總是處於信號掩碼中。
#include <signal.h>
int sigprocmask (int how, const sigset_t* set, sigset_t* oldset);
成功返回0,失敗返回-1。
how - 修改信號掩碼的方式,可取以下值:
SIG_BLOCK: 新掩碼是當前掩碼和set的並集 (將set加入信號掩碼);
SIG_UNBLOCK: 新掩碼是當前掩碼和set補集的交集 (從信號掩碼中刪除set);
SIG_SETMASK: 新掩碼即set(將信號掩碼設為set)。
set - NULL則忽略。
oset - 備份以前的信號掩碼,NULL則不備份。
int sigpending (sigset_t* set);
set - 輸出,調用進程當前處於未決狀態的信號集。
成功返回0,失敗返回-1。
註意:對於不可靠信號,通過sigprocmask函數設置信號掩碼以後,相同的被屏蔽信號只會屏蔽第一個,並在恢復信號掩碼後被遞送,其余的則直接忽略掉。而對於可靠信號,則會在信號屏蔽時按其產生的先後順序排隊,一旦恢復信號掩碼,這些信號會依次被信號處理函數處理。
範例:sigmask.c
九、sigaction-------------
#include <signal.h>
int sigaction ( int signum, // 信號碼 const struct sigaction* act, // 信號處理方式 struct sigaction* oldact // 原信號處理方式 // (可為NULL));
struct sigaction { void (*sa_handler) (int); // 信號處理函數指針1 void (*sa_sigaction) (int, siginfo_t*, void*); // 信號處理函數指針2 sigset_t sa_mask; // 信號掩碼 int sa_flags; // 信號處理標誌 void (*sa_restorer)(void); // 保留,NULL};
成功返回0,失敗返回-1。
1. 缺省情況下,在信號處理函數的執行過程中, 會自動屏蔽這個正在被處理的信號, 而對於其它信號則不會屏蔽。 通過sigaction::sa_mask成員可以人為指定, 在信號處理函數的執行過程中, 需要加入進程信號掩碼中的信號, 並在信號處理函數執行完之後, 自動解除對這些信號的屏蔽。
2. sigaction::sa_flags可為以下值的位或:
SA_ONESHOT/SA_RESETHAND - 執行完一次信號處理函數後, 即將對此信號的處理恢復為 默認方式(這也是老版本 signal函數的缺省行為)。
SA_NODEFER/SA_NOMASK - 在信號處理函數的執行過程中, 不屏蔽這個正在被處理的信號。
SA_NOCLDSTOP - 若signum參數取SIGCHLD, 則當子進程暫停時, 不通知父進程。
SA_RESTART - 系統調用一旦被signum參數 所表示的信號中斷, 會自行重啟。
SA_SIGINFO - 使用信號處理函數指針2, 通過該函數的第二個參數, 提供更多信息。
typedef struct siginfo { pid_t si_pid; // 發送信號的PID sigval_t si_value; // 信號附加值 // (需要配合sigqueue函數) ...} siginfo_t;
typedef union sigval { int sival_int; void* sival_ptr;} sigval_t;
範例:sigact.c
十、sigqueue------------
#include <signal.h>
int sigqueue (pid_t pid, int sig, const union sigval value);
向pid進程發送sig信號,附加value值(整數或指針)。成功返回0,失敗返回-1。
範例:sigque.c
註意:sigqueue函數對不可靠信號不做排隊,會丟失信號。
十一、計時器------------
1. 系統為每個進程維護三個計時器~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1) 真實計時器: 程序運行的實際時間。
2) 虛擬計時器: 程序運行在用戶態所消耗的時間。
3) 實用計時器: 程序運行在用戶態和內核態所消耗的時間之和。
實際時間(真實計時器) = 用戶時間(虛擬計時器) + 內核時間 + 睡眠時間 ------------------------------- (實用計時器)
2. 為進程設定計時器~~~~~~~~~~~~~~~~~~~
1) 用指定的初始間隔和重復間隔為進程設定好計時器後, 該計時器就會定時地向進程發送時鐘信號。
2) 三個計時器所發送的時鐘信號分別為:
SIGALRM - 真實計時器SIGVTALRM - 虛擬計時器SIGPROF - 實用計時器
3) 獲取/設置計時器
#include <sys/time.h>
int getitimer (int which, struct itimerval* curr_value);
獲取計時器設置。成功返回0,失敗返回-1。
int setitimer (int which, const struct itimerval* new_value, struct itimerval* old_value);
設置計時器。成功返回0,失敗返回-1。
which - 指定哪個計時器,取值:
ITIMER_REAL: 真實計時器; ITIMER_VIRTUAL: 虛擬計時器; ITIMER_PROF: 實用計時器。
curr_value - 當前設置。
new_value - 新的設置。
old_value - 舊的設置(可為NULL)。
struct itimerval { struct timeval it_interval; // 重復間隔(每兩個時鐘信號的時間間隔), // 取0將使計時器在發送第一個信號後停止 struct timeval it_value; // 初始間隔(從調用setitimer函數到第一次發送 // 時鐘信號的時間間隔),取0將立即停止計時器};
struct timeval { long tv_sec; // 秒數 long tv_usec; // 微秒數};
範例:timer.c

來自為知筆記(Wiz)

7.6-UC-第六課:信號處理