信號處理器會中斷主程序的執行,直到信號處理器函數返回時,主程序從中斷處繼續執行

圖片來源于Linux/Unix系統編程手冊
### 同類信號阻塞
  在執行某信號的處理器函數會阻塞同類信號(即處理器函數在執行過程,如果再次產生同類型信號,會將信號標記為等待狀態,當處理器函數返回時再進行傳遞;
若有有多個,則只會傳遞一次)
### 安全性問題
  信號處理器會中斷主程序運行,然后變成兩條獨立的線程(信號處理器和主程序),兩條線程對全局變量的修改會相互影響對方,這就是所謂的不安全;(問題是既
然中斷了主程序執行,那么兩個線程應該是不會同時調用某個函數或修改某個全局變量啊???原來是執行到一半的時候被中斷了,額...,還是不懂...)
如何規避?
- 確保信號處理器函數使用可重入函數和異步信號安全函數
- 在主程序執行不安全函數時,阻塞信號的傳遞(借助于sigprocmask函數)
#### 重置errno問題
由于信號器處理器會修改errno的值,從而影響到主程序的errno,解決方法在入口處保存errno,在返回時恢復errno舊值
```c
void handler(int sig) {
int oldErrno = errno;
// do something
errno = oldErrno;
}
```
### 全局變量聲明
```c
volatile sig_atomic_t flag
```
- 主程序和信號處理器函數共享全局變量,聲明變量的時候使用volatile關鍵字
- sig_atomic_t整數數據類型,作用是保證讀寫操作的原子性
### 終止信號處理器其他方法
- 使用_exit()退出進程,不要使用exit(),exit()不是安全函數,它在調用_exit()函數之前會刷新stdio的緩沖區(是不是會刷新主程序的緩沖區??)
### 備選棧處理信號
  一般是內存不夠了,才會需要備選棧的情況,先忽略它,有需要在回頭總結下 todo review
### 非本地跳轉技術
  非本地跳轉即從信號處理器跳轉到主程序?實在看不懂,先跳過...
```c
#include <setjmp.h>
sigsetjump(sigjmp_buf env, int savesigs)
siglongjump(sigjmp_buf env, int val)
// return 0 on initial call, nonzero on return via siglongjump()
```
- savesigs不為0,會將當前信號掩碼添加到env中,之后可以通過siglongjump恢復
- 需要宏定義USE_SIGSETJMP
### SA_SIGINFO
由上一節我們知道sigaction結構有一個位掩碼flags,當sigaction.flags=SA_SIGINFO時,可以傳遞更多的信號信息;例如信號處理器攜帶了額外信息,同時信號處
理器應該聲明如下:
```c
void handler(int sig, siginfo_t *siginfo, void *context);
```
相對的,sigaction的結構多一個sa_sigaction字段,如下:
```c
struct sigaction {
union {
void (*sa_hander)(int);
void (*sa_sigaction)(int,siginfo_t *,void *);
},
sigset_t sa_mask; // 信號掩碼
int flags; // 位掩碼,標識信號處理器的行為
void (*sa_restorer)(void)
}
```
要獲取signal.h對siginfo_t的聲明,需要將特性測試宏_POSIX_C_SOURCE的值定義為大于或等于199309,siginfo結構如下:

重點關注下面幾個:
- si_value包含調用sigqueue發送信號伴隨的數據
- si_pid發送進程id
- si_fd包含于io相關的文件描述符號
- si_code信號來源;例如通過sigqueue發送的實時信號,該字段為SI_QUEUE
### 系統調用中斷和重啟
  如果有一個阻塞的系統調用,當信號來傳遞過來時,主程序被中斷,當信號處理器完成返回時,系統調用失敗,并將errno設置為EINTR;如果要繼續運行主程序,可以將sigaction.flags設置SA_RESTART(所謂的重啟是主程序從中斷處繼續執行還是整個主程序重啟?)
### 信號同步產生和異步產生
- 同步信號,執行進程知道信號產生的時機,例如執行進程調用raise()向自己發送一個信號
- 異步信號,執行進程不知道信號產生的時機,例如信號是由內核或其他進程發出的
### 信號傳遞時機和順序
  當進程解除對一組等待標準信號的阻塞的時候,與各個信號產生時機先后無關,等待信號會根據信號編號從小到大優先傳遞;執行流程如下:

### 實時信號
實時信號和標準信號的區別:
- 標準信號編號為1~31,而實時信號為32~63
- 同類型信號被阻塞時產生多次,當解除阻塞時,標準信號只會傳遞一次,而實時信號因為會排隊等待,可以都被傳遞
- 實時信號可以通過設置SA_SIGINFO傳遞給信號處理器額外的信息
- 一組標準信號在解除阻塞時,傳遞順序按編號升序傳遞;實時信號根據信號發送順序傳遞
- SIGRTMAX表示實時信號最大值
- SIGRTMIN表示實時信號最小值
發送實時信號如下:
```c
// Returns 0 on success , or -1 on error
sigqueue(pid_t pid, int sig, const union sigval value)
// union sigval
union sigval {
int sival_int; // 保存整數
void *sival_ptr; // 一個指針(一般用不到)
}
```
排隊信號數量有限制,當sigqueue發送數量超過這個限制,調用失敗并會設置errno為EAGAIN,表示需要再次發送該信號
demo:
```c
#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include "../tlpi_hdr.h"
int main(int argc, char const *argv[])
{
union sigval sv;
pid_t pid;
int sig, num, value;
if (argc < 4 || strcmp(argv[1], "--help") == 0)
{
usageErr("%s pid sig value [num]\n", argv[0]);
}
printf("%s: pid: %ld; uid: %ld\n", argv[0], (long)getpid(), (long)getuid());
pid = getLong(argv[1], GN_GT_0, "pid");
sig = getInt(argv[2], GN_GT_0, "sig");
value = getInt(argv[3], 0, "value");
num = argc > 4 ? getInt(argv[4], GN_GT_0, "num") : 1;
for (int i = 0; i < num; ++i)
{
sv.sival_int = value + i;
if (sigqueue(pid, sig, sv) == 1)
{
errExit("sigqueue failed \n");
}
}
return 0;
}
```
### 使用掩碼來等待信號sigsuspend
  使用掩碼阻塞信號,防止信號中止主程序某關鍵代碼段的執行;如果中斷處發送在解除信號和pasue掛起進程之間,會導致信號處理器返回的時候,主程序將
再次被掛起直到下一次信號到來(正常的情況是解除和掛起進程視為一個原子操作,掛起進程后接收到信號,觸發信號處理器,處理后退出進程);
sigsuspend把解除信號阻塞和掛起進程封裝成一個原子操作
```c
// returns -1 with errno set to EINTR
sigsuspend(sigset_t *mask)
```
- mask代替進程的信號掩碼,例如要解除某個信號,就把這個信號去掉,不要放在mask中
- 當處理器返回,sigsuspend會將進程的信號掩碼恢復到調用前的值
一個簡單的例子:
```c
#define _GNU_SOURCE
#include <signal.h>
#include "../tlpi_hdr.h"
void handler(int sig)
{
printf("sig %i %s\n", sig, strsignal(sig));
}
int main(int argc, char const *argv[])
{
struct sigaction sa;
sigset_t blockMask, prevMask;
printf("%s: pid:%ld; uid:%ld\n", argv[0], (long)getpid(), (long)getuid());
sigaddset(&blockMask, SIGINT);
sigaddset(&blockMask, SIGQUIT);
// 阻塞SIGINT和SIGQUIT,并且保留原來的信號掩碼,用于恢復
if(sigprocmask(SIG_BLOCK, &blockMask, &prevMask) == -1)
{
errExit("sigprocmask failed \n");
}
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handler;
if (sigaction(SIGINT, &sa, NULL) == -1)
{
errExit("sigaction SIGINT failed \n");
}
if (sigaction(SIGQUIT, &sa, NULL) == -1)
{
errExit("sigaction SIGQUIT failed \n");
}
// do something (不能被SIGINT和SIGQUIT信號中斷的代碼段,以sleep代替)
sleep(10);
// 解除信號阻塞并掛起進程
if (sigsuspend(&prevMask) == -1 && errno != EINTR)
{
errExit("sigsuspend failed");
}
// 恢復信號掩碼
if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
{
errExit("sigprocmask failed \n");
}
return 0;
}
```
### 同步等待信號到達
### signalfd應用
### 問題
- 信號處理器如何中斷處于阻塞狀態的系統調用,如何重啟系統調用
### 參考
- Linux系統編程手冊(上冊)
- php
- 編譯安裝
- 基本概念
- 垃圾回收機制
- 生命周期
- zval底層實現
- c擴展開發
- gdb調試工具
- 自定義擴展簡單demo
- 鉤子函數
- 讀取php.ini配置
- 數組
- 函數
- 類
- yaf擴展底層源碼
- swoole擴展底層源碼
- memoryGlobal內存池
- swoole協程使用記錄
- 單點登錄sso原理
- compser使用
- session實現機制
- c & linux
- gcc
- 指針
- 結構體,聯合和位字段
- 宏定義井號說明
- printf家族函數和可變參數
- 共享函數
- 靜態庫和動態庫
- makefile自動化構建
- 信號一
- 信號二
- inotify監控文件事件
- socket編程
- 簡介
- UNIX DOMAIN
- Internet DOMAIN
- TCP/IP
- 文件IO多路復用
- 內存管理
- 進程組,會話和控制終端
- daemon守護進程
- 多進程
- 多線程
- 常用進制轉換
- go
- 入門知識
- 字節和整數裝換
- python
- redis
- 應用場景
- 消息隊列
- 熱點數據
- 掃碼登錄
- 訂閱發布
- 次數限制
- 搶購超賣
- 持久化機制
- mysql
- 工作流程
- MyISAM和InnoDB區別
- 用戶和權限管理
- 執行計劃
- sql優化
- 事務和鎖
- 慢查詢日志
- case...when...then...end用法
- sql
- 參考
- linux
- 內核參數優化
- 防火墻設置
- docker
- docker入門知識
- 算法
- 多維數組合
- DFA算法
- 紅包金額分配