c++ sleep函數_Linux 多線程應用中如何編寫安全的信號處理函數

關于代碼的可重入性,設計開發人員一般只考慮到線程安全,異步信號處理函數的安全卻往往被忽略。本文首先介紹如何編寫安全的異步信號處理函數;然后舉例說明在多線程應用中如何構建模型讓異步信號在指定的線程中以同步的方式處理。

Linux 多線程應用中編寫安全的信號處理函數

在開發多線程應用時,開發人員一般都會考慮線程安全,會使用 pthread_mutex 去保護全局變量。如果應用中使用了信號,而且信號的產生不是因為程序運行出錯,而是程序邏輯需要,譬如 SIGUSR1、SIGRTMIN 等,信號在被處理后應用程序還將正常運行。在編寫這類信號處理函數時,應用層面的開發人員卻往往忽略了信號處理函數執行的上下文背景,沒有考慮編寫安全的信號處理函數的一些規則。本文首先介紹編寫信號處理函數時需要考慮的一些規則;然后舉例說明在多線程應用中如何構建模型讓因為程序邏輯需要而產生的異步信號在指定的線程中以同步的方式處理。

線程和信號

Linux 多線程應用中,每個線程可以通過調用 pthread_sigmask() 設置本線程的信號掩碼。一般情況下,被阻塞的信號將不能中斷此線程的執行,除非此信號的產生是因為程序運行出錯如 SIGSEGV;另外不能被忽略處理的信號 SIGKILL 和 SIGSTOP 也無法被阻塞。

當一個線程調用 pthread_create() 創建新的線程時,此線程的信號掩碼會被新創建的線程繼承。

POSIX.1 標準定義了一系列線程函數的接口,即 POSIX threads(Pthreads)。Linux C 庫提供了兩種關于線程的實現:LinuxThreads 和 NPTL(Native POSIX Threads Library)。LinuxThreads 已經過時,一些函數的實現不遵循POSIX.1 規范。NPTL 依賴 Linux 2.6 內核,更加遵循 POSIX..1 規范,但也不是完全遵循。

基于 NPTL 的線程庫,多線程應用中的每個線程有自己獨特的線程 ID,并共享同一個進程ID。應用程序可以通過調用kill(getpid(),signo) 將信號發送到進程,如果進程中當前正在執行的線程沒有阻礙此信號,則會被中斷,線號處理函數會在此線程的上下文背景中執行。應用程序也可以通過調用 pthread_kill(pthread_t thread, int sig)將信號發送給指定的線程,則線號處理函數會在此指定線程的上下文背景中執行。

基于 LinuxThreads 的線程庫,多線程應用中的每個線程擁有自己獨特的進程 ID,getpid() 在不同的線程中調用會返回不同的值,所以無法通過調用 kill(getpid(),signo)將信號發送到整個進程。

下文介紹的在指定的線程中以同步的方式處理異步信號是基于使用了 NPTL 的 Linux C 庫。請參考“Linux 線程模型的比較:LinuxThreads 和 NPTL”和“pthreads(7) - Linux man page”進一步了解 Linux 的線程模型,以及不同版本的 Linux C 庫對 NPTL 的支持。

編寫安全的異步信號處理函數

信號的產生可以是:

  • 用戶從控制終端終止程序運行,如 Ctrk + C 產生 SIGINT;
  • 程序運行出錯時由硬件產生信號,如訪問非法地址產生 SIGSEGV;
  • 程序運行邏輯需要,如調用 kill、raise 產生信號。

因為信號是異步事件,即信號處理函數執行的上下文背景是不確定的,譬如一個線程在調用某個庫函數時可能會被信號中斷,庫函數提前出錯返回,轉而去執行信號處理函數。對于上述第三種信號的產生,信號在產生、處理后,應用程序不會終止,還是會繼續正常運行,在編寫此類信號處理函數時尤其需要小心,以免破壞應用程序的正常運行。關于編寫安全的信號處理函數主要有以下一些規則:

  • 信號處理函數盡量只執行簡單的操作,譬如只是設置一個外部變量,其它復雜的操作留在信號處理函數之外執行;
  • errno 是線程安全,即每個線程有自己的 errno,但不是異步信號安全。如果信號處理函數比較復雜,且調用了可能會改變 errno 值的庫函數,必須考慮在信號處理函數開始時保存、結束的時候恢復被中斷線程的 errno 值;
  • 信號處理函數只能調用可以重入的 C 庫函數;譬如不能調用 malloc(),free()以及標準 I/O 庫函數等;
  • 信號處理函數如果需要訪問全局變量,在定義此全局變量時須將其聲明為 volatile,以避免編譯器不恰當的優化。

從整個 Linux 應用的角度出發,因為應用中使用了異步信號,程序中一些庫函數在調用時可能被異步信號中斷,此時必須根據errno 的值考慮這些庫函數調用被信號中斷后的出錯恢復處理,譬如socket 編程中的讀操作:

rlen = recv(sock_fd, buf, len, MSG_WAITALL); if ((rlen == -1) && (errno == EINTR)){// this kind of error is recoverable, we can set the offset change //‘rlen’ as 0 and continue to recv}

注:需要C/C++ Linux服務器開發學習資料加qun獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等),免費分享

edf8a87f0453d259e8a9edf4031cf371.png

在指定的線程中以同步的方式處理異步信號

如上文所述,不僅編寫安全的異步信號處理函數本身有很多的規則束縛;應用中其它地方在調用可被信號中斷的庫函數時還需考慮被中斷后的出錯恢復處理。這讓程序的編寫變得復雜,幸運的是,POSIX.1 規范定義了sigwait()、 sigwaitinfo()和 pthread_sigmask() 等接口,可以實現:

  • 以同步的方式處理異步信號;
  • 在指定的線程中處理信號。

這種在指定的線程中以同步方式處理信號的模型可以避免因為處理異步信號而給程序運行帶來的不確定性和潛在危險。

sigwait

sigwait() 提供了一種等待信號的到來,以串行的方式從信號隊列中取出信號進行處理的機制。sigwait()只等待函數參數中指定的信號集,即如果新產生的信號不在指定的信號集內,則 sigwait()繼續等待。對于一個穩定可靠的程序,我們一般會有一些疑問:

  • 多個相同的信號可不可以在信號隊列中排隊?
  • 如果信號隊列中有多個信號在等待,在信號處理時有沒有優先級規則?
  • 實時信號和非實時信號在處理時有沒有什么區別?

用一小段測試程序來測試 sigwait 在信號處理時的一些規則。

清單1、sigwait_test.c

#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>void sig_handler(int signum)
{printf("Receive signal. %dn", signum);
}void* sigmgr_thread()
{sigset_t   waitset, oset;int        sig;int        rc;pthread_t  ppid = pthread_self();pthread_detach(ppid);sigemptyset(&waitset);sigaddset(&waitset, SIGRTMIN);sigaddset(&waitset, SIGRTMIN+2);sigaddset(&waitset, SIGRTMAX);sigaddset(&waitset, SIGUSR1);sigaddset(&waitset, SIGUSR2);while (1)  {rc = sigwait(&waitset, &sig);if (rc != -1) {sig_handler(sig);} else {printf("sigwaitinfo() returned err: %d; %sn", errno, strerror(errno));}}
}int main()
{sigset_t bset, oset;int             i;pid_t           pid = getpid();pthread_t       ppid;sigemptyset(&bset);sigaddset(&bset, SIGRTMIN);sigaddset(&bset, SIGRTMIN+2);sigaddset(&bset, SIGRTMAX);sigaddset(&bset, SIGUSR1);sigaddset(&bset, SIGUSR2);if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)printf("!! Set pthread mask failedn");kill(pid, SIGRTMAX);kill(pid, SIGRTMAX);kill(pid, SIGRTMIN+2);kill(pid, SIGRTMIN);kill(pid, SIGRTMIN+2);kill(pid, SIGRTMIN);kill(pid, SIGUSR2);kill(pid, SIGUSR2);kill(pid, SIGUSR1);
kill(pid, SIGUSR1);// Create the dedicated thread sigmgr_thread() which will handle signals synchronouslypthread_create(&ppid, NULL, sigmgr_thread, NULL);sleep(10);exit (0);
}

程序編譯運行在 RHEL4 的結果如下:

圖 1. sigwait 測試程序執行結果

4ea891b0d4aa21580cff86081ee5a354.png

從以上測試程序發現以下規則:

  • 對于非實時信號,相同信號不能在信號隊列中排隊;對于實時信號,相同信號可以在信號隊列中排隊。
  • 如果信號隊列中有多個實時以及非實時信號排隊,實時信號并不會先于非實時信號被取出,信號數字小的會先被取出:如 SIGUSR1(10)會先于 SIGUSR2 (12),SIGRTMIN(34)會先于 SIGRTMAX (64), 非實時信號因為其信號數字小而先于實時信號被取出。

sigwaitinfo() 以及 sigtimedwait() 也提供了與 sigwait()函數相似的功能。

Linux 多線程應用中的信號處理模型

在基于 Linux 的多線程應用中,對于因為程序邏輯需要而產生的信號,可考慮調用 sigwait()使用同步模型進行處理。其程序流程如下:

  1. 主線程設置信號掩碼,阻礙希望同步處理的信號;主線程的信號掩碼會被其創建的線程繼承;
  2. 主線程創建信號處理線程;信號處理線程將希望同步處理的信號集設為 sigwait()的第一個參數。
  3. 主線程創建工作線程。

圖 2. 在指定的線程中以同步方式處理異步信號的模型

12eb85c60b462c335ef5e027a6c1acd9.png

代碼示例

以下為一個完整的在指定的線程中以同步的方式處理異步信號的程序。

主線程設置信號掩碼阻礙 SIGUSR1 和 SIGRTMIN 兩個信號,然后創建信號處理線程sigmgr_thread()和五個工作線程worker_thread()。主線程每隔10秒調用kill() 對本進程發送 SIGUSR1 和 SIGTRMIN 信號。信號處理線程 sigmgr_thread()在接收到信號時會調用信號處理函數 sig_handler()。

程序編譯:gcc -o signal_sync signal_sync.c -lpthread

程序執行:./signal_sync

從程序執行輸出結果可以看到主線程發出的所有信號都被指定的信號處理線程接收到,并以同步的方式處理。

清單2. signal_sync.c

#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>void sig_handler(int signum)
{static int j = 0;static int k = 0;pthread_t  sig_ppid = pthread_self(); // used to show which thread the signal is handled in.if (signum == SIGUSR1) {printf("thread %d, receive SIGUSR1 No. %dn", sig_ppid, j);j++;//SIGRTMIN should not be considered constants from userland, //there is compile error when use switch case} else if (signum == SIGRTMIN) {printf("thread %d, receive SIGRTMIN No. %dn", sig_ppid, k);k++;}
}void* worker_thread()
{pthread_t  ppid = pthread_self();pthread_detach(ppid);while (1) {printf("I'm thread %d, I'm aliven", ppid);sleep(10);}
}void* sigmgr_thread()
{sigset_t   waitset, oset;siginfo_t  info;int        rc;pthread_t  ppid = pthread_self();pthread_detach(ppid);sigemptyset(&waitset);sigaddset(&waitset, SIGRTMIN);sigaddset(&waitset, SIGUSR1);while (1)  {rc = sigwaitinfo(&waitset, &info);if (rc != -1) {printf("sigwaitinfo() fetch the signal - %dn", rc);sig_handler(info.si_signo);} else {printf("sigwaitinfo() returned err: %d; %sn", errno, strerror(errno));}}
}int main()
{sigset_t bset, oset;int             i;pid_t           pid = getpid();pthread_t       ppid;// Block SIGRTMIN and SIGUSR1 which will be handled in //dedicated thread sigmgr_thread()// Newly created threads will inherit the pthread mask from its creator sigemptyset(&bset);sigaddset(&bset, SIGRTMIN);sigaddset(&bset, SIGUSR1);if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)printf("!! Set pthread mask failedn");// Create the dedicated thread sigmgr_thread() which will handle // SIGUSR1 and SIGRTMIN synchronouslypthread_create(&ppid, NULL, sigmgr_thread, NULL);// Create 5 worker threads, which will inherit the thread mask of// the creator main threadfor (i = 0; i < 5; i++) {pthread_create(&ppid, NULL, worker_thread, NULL);}// send out 50 SIGUSR1 and SIGRTMIN signalsfor (i = 0; i < 50; i++) {kill(pid, SIGUSR1);printf("main thread, send SIGUSR1 No. %dn", i);kill(pid, SIGRTMIN);printf("main thread, send SIGRTMIN No. %dn", i);sleep(10);}exit (0);
}

注意事項

在基于 Linux 的多線程應用中,對于因為程序邏輯需要而產生的信號,可考慮使用同步模型進行處理;而對會導致程序運行終止的信號如 SIGSEGV 等,必須按照傳統的異步方式使用signal()、 sigaction()注冊信號處理函數進行處理。這兩種信號處理模型可根據所處理的信號的不同同時存在一個 Linux 應用中:

  • 不要在線程的信號掩碼中阻塞不能被忽略處理的兩個信號 SIGSTOP 和 SIGKILL。
  • 不要在線程的信號掩碼中阻塞 SIGFPE、SIGILL、SIGSEGV、SIGBUS。
  • 確保 sigwait() 等待的信號集已經被進程中所有的線程阻塞。
  • 在主線程或其它工作線程產生信號時,必須調用 kill() 將信號發給整個進程,而不能使用 pthread_kill()發送某個特定的工作線程,否則信號處理線程無法接收到此信號。
  • 因為 sigwait()使用了串行的方式處理信號的到來,為避免信號的處理存在滯后,或是非實時信號被丟失的情況,處理每個信號的代碼應盡量簡潔、快速,避免調用會產生阻塞的庫函數。

小結

在開發 Linux 多線程應用中, 如果因為程序邏輯需要引入信號, 在信號處理后程序仍將繼續正常運行。在這種背景下,如果以異步方式處理信號,在編寫信號處理函數一定要考慮異步信號處理函數的安全; 同時, 程序中一些庫函數可能會被信號中斷,錯誤返回,這時需要考慮對 EINTR 的處理。另一方面,也可考慮使用上文介紹的同步模型處理信號,簡化信號處理函數的編寫,避免因為信號處理函數執行上下文的不確定性而帶來的風險。

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

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

相關文章

css特殊情況

如果一個父級div和一個子級div&#xff0c;要給父級div加&#xff08;opacity&#xff09;透明度那子級div也會繼承父級元素的透明度。那給父級元素加透明度就不用opacity加透明度&#xff0c;使用background:rgba(120,120,120,0.7);實現效果&#xff0c;r,g,b分別代表紅&#…

CoreAnimation (CALayer 動畫)

CoreAnimation基本介紹&#xff1a; CoreAnimation動畫位于iOS框架的Media層CoreAnimation動畫實現需要添加QuartzCore.FrameworkCoreAnimation基本上是LayerAnimationCoreAnimation分類&#xff1a; CoreAnimation作用&#xff1a; CoreAnimation CALayer基本介紹 CALayer的常…

匯編為什么分段執行總是執行不了_iOS匯編教程(六)CPU 指令重排與內存屏障...

系列文章iOS 匯編入門教程(一)ARM64 匯編基礎iOS 匯編入門教程(二)在 Xcode 工程中嵌入匯編代碼iOS 匯編入門教程(三)匯編中的 Section 與數據存取iOS 匯編教程(四)基于 LLDB 動態調試快速分析系統函數的實現iOS 匯編教程(五)Objc Block 的內存布局和匯編表示前言具有 ARM 體系…

GD32 使用stm32 固件庫

1、 系統 1) 晶振起振區別 描述&#xff1a;啟動時間&#xff0c;GD32 與STM32 啟動時間都是2ms&#xff0c;實際上GD 的執行效率快&#xff0c;所以ST 的HSE_STARTUP_TIMEOUT ((uint16_t)0x0500)是2ms&#xff0c;但是這個宏定義值在GD 上時間就更加短了&#xff0c;所以要加大…

干將莫邪

干將莫邪也為凡鐵鑄成&#xff0c;只是善加鍛造、融入心神&#xff0c;而成上古神兵。寶劍從來都是雙刃&#xff0c;正邪之道&#xff0c;存乎一心。

js反混淆還原工具_SATURN反混淆框架

本文為看雪論壇精華文章看雪論壇作者ID&#xff1a;夢野間摘要&#xff1a;近幾年&#xff0c;軟件的混淆強度一直在不斷提升。基于編譯器的混淆已經成為業界事實上的標準&#xff0c;最近的一些論文也表明軟件的保護方式使用的是編譯器級別的混淆。在這篇文章中&#xff0c;我…

android 彈起鍵盤把ui頂上去的解決辦法

鍵盤輸入框上面的ui布局必須為Relative相對布局。然后設置 <activityandroid:name".activity.HomeActivity"Android:windowSoftInputMode"adjustPan|stateHidden"></activity>轉載于:https://www.cnblogs.com/zhaoleigege/p/5925831.html

python 多線程并發_尋找python大神!!!python如何多線程并發?

不是大神。嘗試回答一下。 首先解釋下什么叫做線程&#xff0c;什么叫做進程&#xff0c;在解釋這兩個概念前&#xff0c;我們還需要明白什么叫做GIL全局解釋器鎖。GIL 全局解釋器鎖&#xff1a; GIL(全局解釋器鎖&#xff0c;GIL 只有cpython有)&#xff1a;在同一個時刻&…

Nginx/Apache發大招

導讀網站程序的上傳目錄通常是不需要PHP執行解釋權限&#xff0c;通過限制目錄的PHP執行權限可以提網站的安全性&#xff0c;減少被攻擊的機率。下面和大家一起分享下如何在Apache和Nginx禁止上傳目錄里PHP的執行權限。 Apache下禁止指定目錄運行PHP腳本在虛擬主機配置文件中增…

第二輪沖刺-Runner站立會議08

今天完成的內容&#xff1a;簡單的做了一下主界面的美化和日歷界面的美化 遇到的問題&#xff1a;美化按鈕還不能自己自定義按鈕 如何解決&#xff1a;暫無思路 明天將要進行的內容&#xff1a;調試bug 轉載于:https://www.cnblogs.com/Againzg/p/5544301.html

STM32串口通信中使用printf發送數據配置方法 開發環境 Keil

STM32串口通信中使用printf發送數據配置方法(開發環境 Keil RVMDK) 已有 12456 次閱讀2011-6-29 23:29 | 在STM32串口通信程序中使用printf發送數據&#xff0c;非常的方便。可在剛開始使用的時候總是遇到問題&#xff0c;常見的是硬件訪真時無法進入main主函數&#xff0c;其實…

dmp文件查看表空間_innoDb文件

一&#xff0e;文件總體概述InnoDb文件主要有以下文件1. 參數文件&#xff1a;啟動需要的各種參數作2. 日志文件&#xff1a;記錄mysql實例某種條件做出的響應而寫入的文件&#xff0c;如錯誤日志、二進制日志、慢查詢日志、查詢日志等3. Socket文件&#xff1a;連接需要的文件…

論文筆記之:Deep Attention Recurrent Q-Network

Deep Attention Recurrent Q-Network 5vision groups 摘要&#xff1a;本文將 DQN 引入了 Attention 機制&#xff0c;使得學習更具有方向性和指導性。&#xff08;前段時間做一個工作打算就這么干&#xff0c;誰想到&#xff0c;這么快就被這幾個孩子給實現了&#xff0c;自愧…

Codeforces Round #354 (Div. 2)

貪心 A Nicholas and Permutation #include <bits/stdc.h>typedef long long ll; const int N 1e5 5; int a[105]; int pos[105];int main() {int n;scanf ("%d", &n);for (int i1; i<n; i) {scanf ("%d", ai);pos[a[i]] i;}int ans abs …

linux c程序中內核態與用戶態內存存儲問題

Unix/Linux的體系架構 如上圖所示&#xff0c;從宏觀上來看&#xff0c;Linux操作系統的體系架構分為用戶態和內核態&#xff08;或者用戶空間和內核&#xff09;。內核從本質上看是一種軟件——控制計算機的硬件資源&#xff0c;并提供上層應用程序運行的環境。用戶態即上層應…

線程自動退出_C++基礎 多線程筆記(一)

join & detachjoin和detach為最基本的用法&#xff0c;join可以使主線程&#xff08;main函數&#xff09;等待子線程&#xff08;自定義的function_1函數&#xff09;完成后再退出程序&#xff0c;而detach可以使子線程與主線程毫無關聯的獨立運行&#xff0c;當主線程執行…

WEB在線預覽PDF

這是我在博客園發表的第一篇文章。以后會陸續把在線預覽其他格式文檔的解決方案發表出來。 解決思路&#xff1a;把pdf轉換成html顯示。 在線預覽pdf我暫時了解3種解決方案&#xff0c;歡迎大家補充。 方案一&#xff1a; 利用pdf2html軟件將PDF轉換成HTML。 用法: PDF2HTML [選…

[算法]判斷一個數是不是2的N次方

如果一個數是2^n&#xff0c;說明這個二進制里面只有一個1。除了1. a (10000)b a-1 (01111)b a&(a-1) 0。 如果一個數不是2^n&#xff0c; 說明它的二進制里含有多一個1。 a (1xxx100)b a-1(1xxx011)b 那么 a&(a-1)就是 (1xxx000)b&#xff0c; 而不會為0。 所以可…

VMware Ubuntu 全屏問題解決

在終端中輸入&#xff1a; sudo apt install open-vm* 回車 自動解決

數組拼接時中間怎么加入空格_【題解二維數組】1123:圖像相似度

1123&#xff1a;圖像相似度時間限制: 1000 ms 內存限制: 65536 KB【題目描述】給出兩幅相同大小的黑白圖像(用0-1矩陣)表示&#xff0c;求它們的相似度。說明&#xff1a;若兩幅圖像在相同位置上的像素點顏色相同&#xff0c;則稱它們在該位置具有相同的像素點。兩幅圖像的…