linux 信號_Linux信號機制

3673c2ea393721817eeba68ec4ac784c.png

信號就是一條消息,通知進程系統中發生了什么事,每種信號都對應著某種系統事件。一般的底層硬件異常是由內核的異常處理程序處理的,它對用戶進程來說是透明的。而信號機制,提供了一種方法通知用戶進程發生了這些異常。

例如,一個進程試圖除0,會引發內核向他發送SIGFPE信號;執行非法指令會引發SIGILL信號;非法內存訪問引發SIGSEGV;當你從鍵盤上鍵入Ctrl + C會引發SIGINT;當某個子進程結束會引發內核向其父進程發送SIGCHLD信號,等等。具體請看下圖:

4fd29637229f2ea62e15275d505ba9c3.png

1. 信號術語與原則

1.1信號發送

當內核檢測到某種系統事件(除零錯誤或子進程終止等等)或一個進程調用了kill函數顯式的要求內核發送一個信號給目的進程時,內核會通過更新目的進程上下文中的某個狀態而達到向它發送一個信號的目的。發送信號的方式為:

  • 命令行:kill -signum PID命令,向進程號為PID的進程發送signum信號;
  • 鍵盤:通過鍵盤發送特定信號,Ctrl + C 向前臺進程組中的每個進程發送SIGINT終止信號;Ctrl + Z 向前臺進程組中的每個進程發送SIGTSTP暫停信號;
  • 函數alarm: 使內核在一段時間(secs秒)后,向自己發送SIGALRM信號;

```c #include

unsigned int alarm(unsigned int secs); //返回:待處理的鬧鐘在被發送前還剩余的秒數,若之前沒有待處理的鬧鐘,則返回0 //若secs = 0,不會調度安排新的鬧鐘。 ```

函數kill:進程通過調用kill函數發送信號給其它進程(包括自己)。

```c #include #include

int kill(pid_t pid, int sig); //成功返回0,失敗返回-1。 ```

  • pid > 0 :發送信號sig給進程pid;
  • pid = 0 :發送信號給自己所在進程組中的每個進程,包括自己。
  • pid < 0 :發送信號sig給進程-pid。

1.2 信號處理

當進程從系統調用返回或是完成了一次上下文切換而重新取得控制權之前,內核會檢查該進程的待處理信號集(pengding&(~blocked)),如果為空則完成控制權的交接,如果不為空則會讓進程響應該信號集合中信號值最小的那個信號。

目的進程收到信號后有“忽略信號”、“終止進程”和“捕獲信號“這3種方式來響應。其中

  • SIGKILL(終止)和SIGSTOP(暫停)這2個信號不可被忽略,也不能像其它信號一樣可以通過signal函數改變他們的默認處理函數;

```c #include typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler); ```

如果handler = SIG_IGN,那么就忽略類型為signum的信號;
如果handler = SIG_DFL,那么就恢復類型為signum的信號的默認行為;
否則,handler就是用戶自定義的信號處理函數地址。
  • 進程可以有選擇的忽略某些信號(通過將blocked位向量中相應的位置1),即該信號雖然被內核或進程發送了過來,但我可以選擇視而不見。

```c #include

int sigprocmask(int HOW, const sigset_t set, sigset_t oldset);

int sigemptyset(sigset_t set); //初始化set集為空(set = 0); int sigfillset(sigset_t set); //將所有信號都添加進set集(set = 1); int sigaddset(sigset_t set, int signum); int sigdelset(sigset_t set, int signum); //以上5個函數成功返回0,錯誤返回-1 int sigismember(const sigset_t *set, int signum); //是成員返回1,不是返回0,錯誤返回-1 ```

關于sigprocmask函數中的"HOW"有以下幾種可能的取值:
  • SIG_BLOCK:把set集中的信號加到進程的blocked中(blocked |= set);
  • SIG_UNBLOCK:從進程的blocked中刪除set集中的信號(blocked &= ~set);
  • SIG_SETMASK:忽略set集中的信號(blocked = set);

oldset : 如果他是非空的,則將進程原先blocked的值保存在其中。

一下示例展示了臨時忽略SIGINT信號的程序片段:

```c sigset_t mask, oldmask;

sigemptyset(&mask); sigaddset(&mask, SIGINT);

sigprocmask(SIG_BLOCK, &mask, &oldmask); . . //此處的所有語句將不會響應SIGINT信號 . sigprocmask(SIG_SETMASK, &oldmask, NULL); //之后的語句將會正常響應SIGINT信號 ```

  • 任何信號只能被記錄阻塞一次;即如果進程正在執行某類型信號的處理函數,那么在此進程返回主程序前,不管又收到了多少個該類型的信號,它只會被記錄一次(即等到該進程從上次處理函數返回后,它只會再響應一次該類型的信號)。因為內核為每個進程在pending位向量中維護著待處理信號的集合,而在blocked位向量中維護著被阻塞的信號集。每次,收到一個信號,就在blocked相應的位置1,響應一個信號,就在pengding中相應的清零。

2. 安全的信號處理函數

由于信號處理函數和主程序是并發運行的,他們享有相同的全局變量,他們的運行順序是不可預測的,這就導致何時接收到信號的規則往往有違人們的直覺,或者說主程序和子程序間不一定會按照你預想的順序去執行。所以為了防止競爭冒險,在編寫信號處理函數時有幾個保守的原則需要遵守:

  • 處理程序盡可能簡單
  • 在處理程序中僅使用異步安全的函數,也就是說該函數是可重入的(只訪問局部變量)且不能中斷;下圖列出了所有Linux保證安全的系統函數,可以發現許多常見的庫函數(printf、sprintf、malloc、exit等)都不是安全函數,在編寫信號處理函數時要盡量避免使用。

312f22952819291c7bca7a862094eec7.png
為了在信號處理程序中能夠打印一些簡單的消息,我們可以使用一些異步信號安全的系統函數來構建自己的特有包裝函數。作為例子,下面的程序展示了利用異步信號安全的系統函數write編寫自己的SIO(safe I/O)函數。c ssize_t sio_puts(char s[]) { int count = 0; char *str = s; if(!str) _exit(1); while(*str++) count++; return write(STDOUT, s, count); }
  • 保存和恢復error;為了避免處理程序中某些語句的出錯導致error被設置,進而影響主程序中的判斷,在信號處理程序的第一條語句保存原error,在它返回前恢復error。
  • 不管是主程序還是子程序,在訪問全局變量時,都要阻塞所有的信號,以防相互干擾
  • 用volatile聲明全局變量。 volatile要求編譯器每次都是從內存中讀取全局變量的值,而非從緩存中。
  • 使用sig_atomic_t聲明標志。 此處的標志代表在主程序和子程序間傳遞信號的全局變量,因為sig_atomic_t要求編譯器對它的操作是原子的,所以即使沒有阻塞所有信號,它也不會被任何信號打斷。
  • 使用sigaction函數重新包裝signal函數,使得系統自動重啟被中斷的系統調用。 由于一些系統函數(例如read、write、accept等)需要執行較長時間,所以可能會被信號中斷。而在許多較早以前版本的Unix系統中,被中斷的系統調用并不會在信號處理返回后重啟,而是直接返回錯誤并將error設置為EINTR。而sigaction函數可以設置信號處理時的語義。
以下代碼用sigaction函數編寫了signal函數的包裝函數[Signal][1],并且具有如下語義:
  • 只有當前處理的該類型信號被阻塞;
  • 其它信號也不會排隊等待;
  • 只要可能,被中斷的系統調用會自動重啟;
  • 一旦為某信號設置了信號處理程序,它會一直保持到Signal重新為該信號設置SIG_IGN或SIG_DFL的信號處理程序。

``` handler_t Signal(int signum, handler_t handler) { struct sigaction action,oldaction;

action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART;if(sigaction(signum, &action, &oldaction) < 0)unix_error("Signal error");
return(oldaction.sa_handler)

} ```

3.信號的同步

當需要編寫讀寫相同內存位置的并發進程,我們不得不考慮進程間的(既包括進程與進程之間,也包括主進程與子進程之間)競爭關系。這是一個很大的命題,在此限于文章主題,只討論信號之間的競爭關系如何處理。主要分兩個方面,一是隱式競爭,二是顯式競爭。

3.1 避免隱式競爭

考慮一個類似shell的函數功能,父進程在一個全局作業列表中記錄著它的當前子進程,每個作業一個條目。addjob和deletejob函數分別向這個作業列表中添加和刪除作業。父進程每創建一個子進程就把它添加在作業列表中,每當在SIGCHLD信號處理程序中回收一個僵死的子進程時,就在job列表中刪除這個子進程。

void handler(int sig)
{int olderrno = errno;       //保存進程的原error值sigset_t mask_all,prev_all;pid_t pid;sigfillset(&mask_all);      //將所有信號添加到信號集mask_all中while((pid = waitpid(-1, NULL, 0)) > 0){    //回收僵死子進程sigprocmask(SIG_BLOCK, &mask_all, prev_all);    //阻塞(屏蔽)所有信號deletejob(pid);         //從job列表中刪除僵死的子進程條目sigprocmask(SIG_SETMASK, &prev_all, NULL);}if(errno != ECHILD)         //如果父進程的所有子進程都已經回收,則內核發送ECHILD錯誤Unix_error("waitpid error");errno = olderrno;           //恢復進程的原error值
}int main(int argc, char **argv)
{int pid;sigset_t mask_all,mask_one,prev_one;sigfillset(mask_all);sigemptyset(mask_one);sigaddset(&mask_one, SIGCHLD);  Signal(SIGCHLD, handler);       //使用安全的Signal函數設置處理函數initjobs();                     //初始化工作列表while(1){/*在產生子進程前屏蔽SIGCHLD,以防止主進程還沒執行到addjob就已經收到了因子進程終止而發來的SIGCHLD信號,進而進入handler導致在jobs中找不到要刪除的子進程條目*/sigprocmask(SIG_BLOCK, &mask_one, &prev_one);   //頻閉SIGCHLD信號if((pid = fork()) == 0){sigprocmask(SIG_SETMASK, &prev_one, NULL);  //子進程解除頻閉SIGCHLDexecve("/bin/date", argv, NULL);}sigprocmask(SIG_BLOCK, &mask_all, NULL);    //父進程屏蔽所有信號addjob(pid);    sigprocmask(SIG_SETMASK, &prev_one, NULL);  //父進程解除屏蔽}exit(0);
}

3.2 避免顯式競爭

有時候主程序需要顯式地等待某個信號處理運行。例如shell程序,它必須等待當前的前臺進程結束,被SIGCHLD處理程序回收之后,才能繼續創建另一個進程。主進程在等待的這段時間應該干些什么才最好呢?我們可以用一個無限循環語句,讓主進程就在那執行。但這樣也太浪費CPU的資源了;我們也可以用一個sleep或者nanosleep函數讓主進程休眠,但到底休眠多長時間不好把握,間隔太小同樣會造成多次循環,間隔太大,程序又會太慢。

合適的解決辦法是,引入sigsuspend函數:

#include <signal.h>int sigsuspend(const sigset_t *mask);           //返回-1

它暫時掛起調用它的進程,利用參數mask替換當前的信號阻塞集,直到收到一個信號并進入處理程序(如果是終止信號,就直接返回),處理完之后返回主進程,并恢復原來的阻塞集。

下面例子展示了主進程在創建完子進程后,如何利用該函數顯式的等待SIGCHLD的到來,以達到同步的效果。

#include <signal.h>volatile sig_atomic_t pid;void sigchld_handler(int signum)
{int olderror = errno;pid = waitpid(-1, NULL, 0);int errno = olderrno;    
}void sigint_handler(int signum)
{}int main(int argc, char **argv)
{sigset_t mask,prev;Signal(SIGCHLD, sigchld_handler);Signal(SIGINT, sigint_handler);sigemptyset(&mask);sigaddset(&mask, SIGCHLD);while(1){sigprocmask(SIG_BLOCK, &mask, &prev);   //屏蔽SIGCHLD信號if(fork() == 0) //子進程exit(0);pid = 0;while(!pid){    sigsuspend(&prev);  //掛起并等待SIGCHLD信號的到來,其處理函數會使得pid大于0}sigprocmask(SIG_SETMASK, &prev, NULL);printf("...");}exit(0);
}

[1]: 引用:Unix Network Programming: The Sockets Networking API,第三版,第一卷

************************************************

嵌入式 Linux C ARM - 專題 - 簡書?www.jianshu.com
98d1adb13f37f8da0f808767fbeea014.png
CSDN-專業IT技術社區-登錄?blog.csdn.netLeon_Geo - 簡書?www.jianshu.com
b7a2bc151ffbe2ff893cba1ad92859cc.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/259091.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/259091.shtml
英文地址,請注明出處:http://en.pswp.cn/news/259091.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

DOxygen for C++使用說明——添加數學公式

公式 Doxygen允許你把 公式顯示在最終的輸出中&#xff08;這個功能僅限于HTML和輸出&#xff09;.為了可以在HTML documentation顯示公式&#xff08;轉化為圖片&#xff09;&#xff0c;你必須安裝以下軟件&#xff1a; latex: 編譯器, 被用來解析公式, 首先提取公式寫到一…

VC2010下Qt5的中文亂碼問題

要搞清楚這個問題&#xff0c;先要弄明白編碼。但是編碼問題實在太復雜&#xff0c;這里肯定講不開。 我先找一個例子&#xff0c;比如&#xff1a;“中文” 的 Unicode 碼點/UTF8編碼/GBK 分別是多少。 先去這個網站&#xff0c;輸入 “中文” 查詢對應的 Unicode 碼點/UTF8編…

Tomcat 的 DefaultServlet

問題描述&#xff1a; 群里有人測試 Spring MVC&#xff0c;沒有配置任何Controller&#xff0c;只配置了一個view resolver&#xff0c;指定了前綴后綴。 然后&#xff0c;他問的是 當訪問 localhost:8080/test 的時候&#xff0c;為什么會被重定向到 localhost:8080/test/ &a…

Python學習(七)面向對象 ——封裝

Python 類的封裝 承接上一節&#xff0c;學了Student類的定義及實例化&#xff0c;每個實例都擁有各自的name和score。現在若需要打印一個學生的成績&#xff0c;可定義函數 print_score() 該函數為類外的函數&#xff0c;如下&#xff1a; 1 class Student(object):2 def …

spss練習數據_SPSS篇——如何在成千上百萬個數據中標識重復個案

本文就帶大家來學習一個小技巧&#xff0c;如何運用SPSS標識重復個案。我們都知道在Excel中&#xff0c;通常會用到“篩選”功能來選出指定條件相同的單元格。那么在SPSS中&#xff0c;如何在成千上百萬個數據中篩選出重復的個案呢&#xff1f; 小編就是要告訴你&#xff0c;幾…

DOxygen for C++使用說明——Markdown支持

自Doxygen 版本1.8.0&#xff0c;Markdown被引進。 接下來&#xff0c;我們將先簡單介紹標準的Markdown語法&#xff0c;讀者可以進入Markdown官網查詢更詳細的細節。然后討論一下Doxygen支持的Markdown擴展&#xff0c;最后討論一下Doxygen對Markdown標準的實現細節。 Stand…

方程式漏洞之復現window2008/win7 遠程命令執行漏洞

前幾天就想寫的&#xff0c;因為一些緣故就沒寫。此次是在外網環境下進行的。大家在內網中也一個樣。 方法&#xff1a; 使用Eternalblue模塊&#xff0c;劍測是否有漏洞然后msf生成一個dll直接反彈shell. PS&#xff1a;win版本的不知道緣何生成出來的dll是0kb 我就在自己本地…

C++空類和string類

1. 空類 1.1 空類默認哪六個成員函數。 1 class Empty2 {3 public:4 Empty(); //缺省構造函數 Empty e;5 Empty( const Empty& ); //拷貝構造函數 Empty e2(e1);6 ~Empty(); //析構函數7 Empty& operator( const Empty& ); //賦值運算符…

客服會話 小程序 如何發起_小程序、公眾號、App三者如何融合布局?這里有一份避坑指南...

對產品經理來說&#xff0c;小程序無疑是2020年最火爆的詞之一了。我們能看到&#xff0c;就在今年疫情期間&#xff0c;小程序DAU達到4.5億&#xff0c;而超市、生鮮果蔬、社區購物等都同比增長100個點左右&#xff0c;小程序的商業價值很明顯地在快速釋放。小程序如此火爆&am…

DOxygen for C++使用說明——注釋代碼二

這一次我在谷歌搜索中檢索到了Doxygen在github的倉庫&#xff0c;進去一看&#xff0c;令人大喜&#xff0c;github倉庫里含有了一個Doxygen的官方配置文件Doxyfile,于是下載下來&#xff0c;發現Doxyfile已經配置了將倉庫中的\src文件編譯成Documentation,并且將結果放在了dox…

python super()(轉載)

一、問題的發現與提出 在Python類的方法&#xff08;method&#xff09;中&#xff0c;要調用父類的某個方法&#xff0c;在Python 2.2以前&#xff0c;通常的寫法如代碼段1&#xff1a; 代碼段1&#xff1a; class A:def __init__(self):print "enter A"print "…

Swagger+Spring mvc生成Restful接口文檔

2019獨角獸企業重金招聘Python工程師標準>>> Swagger 是一個規范和完整的框架&#xff0c;用于生成、描述、調用和可視化 RESTful 風格的 Web 服務。總體目標是使客戶端和文件系統作為服務器以同樣的速度來更新。文件的方法&#xff0c;參數和模型緊密集成到服務器端…

JavaScript——變量與基本數據類型

前言 JavaScript中的變量為松散類型&#xff0c;所謂松散類型就是指當一個變量被申明出來就可以保存任意類型的值&#xff0c;就是不像SQL一樣申明某個鍵值為int就只能保存整型數值&#xff0c;申明varchar只能保存字符串。一個變量所保存值的類型也可以改變&#xff0c;這在Ja…

vscode可以打開jupyternotebook嗎_剛剛,官方宣布 VS Code 支持 Python 全開發了!

關注Python高校每天早上23:10準時推送北京時間 2019 年 9 月 21 日&#xff0c;PyCon China 2019 在上海舉行。在下午的演講中&#xff0c;來自微軟開發工具事業部的資深研發工程師韓駿做了主題為《Python 與 Visual Studio Code 在人工智能應用中的最佳 Azure 實踐》的演講。在…

C++類的內聯成員函數應放在哪

今天復習C Primer的時候&#xff0c;看到了關于C類的內聯成員函數的放置&#xff0c;應該放在頭文件中。那么這到底是為什么 呢&#xff1f;僅僅是一種代碼規范問題還是必須這樣做呢&#xff1f; 下面我就來講講我自己的理解吧。要徹底理解這個問題&#xff0c;首先就要了解下函…

python selenium自動化(三)Chrome Webdriver的兼容

當一個自動化測試被實現在一個瀏覽器之后&#xff0c;我們會希望我們的測試能夠覆蓋到盡量多的別的瀏覽器。通過跨平臺的測試來保證我們的程序在多個瀏覽器下都能正常工作。 在安裝了selenium之后&#xff0c;firefox webdriver和IE webdriver就已經是ready to use的了&#xf…

NDK 編譯armebai-v7a的非4字節對齊crash Fatal signal 7 (SIGSEGV) 錯誤解決

一直都是編譯armabi的。沒有不論什么問題&#xff0c;這個架構是軟件模擬浮點運算的。后來看到NDK文檔上說armabi-v7a是針對有硬件處理浮點計算的arm cpu的。 于是就改動配置編譯armebai-v7a的so文件。 結果是編譯沒問題。一執行就是crash掉&#xff0c;Fatal signal 7 (SIGSEG…

作業三

作業三 第一章問題&#xff1a;書上寫的“Bug的多少可以直接衡量一個軟件的開發效率、用戶滿意度、可靠性和可維護性”&#xff0c;那么一個比較完好的軟件中一般大概會出現多少Bug? 第二章問題&#xff1a;現在開始訓練寫更多的程序能否更早地達到軟件工程師的標準&#xff1…

springboot默認數據源如何設置連接數_Spring Boot系列之配置數據庫連接池

在實際的應用開發中&#xff0c;與數據庫交互通常使用數據庫連接池來重用Connection對象&#xff0c;減少資源消耗。Spring Boot 的數據源是自動配置的。在 Spring Boot 2.2.1 版本中&#xff0c;有幾種數據源配置可選&#xff0c;它們按照 HikariCP -> Tomcat -> DBCP2 …

使用Qt正則表達式提取全路徑的文件名

問題描述&#xff1a; 給定三個全路徑&#xff0c;例如 path1"C:/Users/asus/Desktop/nefertiti_4465.obj"; path2"C:/Users/asus/Desktop/nefertiti_4465_k1.txt"; path3"C:/Users/asus/Desktop/nefertiti_4465_k2.txt"; 我希望說明path2和pa…