一、可重入函數
參與信號處理的函數必須是可重入函數。
1、何為重入?
假設進程的住控制流程此刻正在調用 foo 函數,就在 foo 函數剛執行到一半的時候,內核向進程遞送了信號 a;假設進程對信號 a 做了捕獲,那麼此時流程將轉入信號 a 的處理函數 siga,而 siga 函數在執行過程中也調用了 foo 函數。於是 foo 函數中的代碼又被執行,剛執行到一半,內核又遞送給了信號 b。假設進程對信號 b 也做了捕獲,並用 sigb 函數來處理,而在 sigb 函數中同樣調用了 foo 函數。
注意,此時的 foo 函數已被重入 3 次,假設該函數中包含了對全局變量、靜態變量、磁盤文件等共享型資源的訪問,其結果將會如何?數據的一致性和安全性如何得到保證?
2、編寫可重入函數
編寫參與信號處理的函數(比如前例中的 foo 函數)時必須要注意,不要對進程在中斷時所做的事情做任何假設。尤其是在對全局變量、靜態局部變量、磁盤文件這些帶有共享特性的對象做寫操作的時候,必須要謹慎。當然,如果能夠不碰或者只讀這些全部對象,那當然再好也沒有。
可重入函數是指可以安全地調用自身(從信號處理中或從其它線程中)的函數。為了使函數可重入,函數決不能操作靜態數據,只訪問在棧裡分配的數據和調用者提供的數據,同時也不得調用任何不可重入的函數。
注意,編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(即P、V操作)等手段對其加以保護。
若對所使用的全局變量不加以保護,則此函數就不具有可重入性,即當多個進程調用此函數時,很有可能使有關全局變量變為不可知狀態。
3、可重入函數
Single UNIX Specification 說明了在信號處理程序中保證調用安全的函數。這些函數時可重入的並被稱為異步信號安全的。除了可重入之外,在信號處理操作期間,它會阻塞任何會引起不一致的信號發送。
下圖列出了異步信號安全的函數,即可重入函數:
4、示例說明
參看:使用可重入函數進行更安全的信號處理
假設Exam是int型全局變量,函數Square_Exam返回Exam平方值。那麼如下函數不具有可重入性。
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
此函數若被多個進程調用的話,其結果可能是未知的,因為當(**)語句剛執行完後,另外一個使用本函數的進程可能正好被激活,那麼當新激活的進程執行到此函數時,將使Exam賦與另一個不同的para值,所以當控制重新回到“temp = Square_Exam( )”後,計算出的temp很可能不是預想中的結果。此函數應如下改進。
unsigned int example( int para )
{
unsigned int temp;
[申請信號量操作] //(1)
Exam = para;
temp = Square_Exam( );
[釋放信號量操作]
return temp;
}
若申請不到“信號量”,說明另外的進程正處於給Exam賦值並計算其平方過程中(即正在使用此信號),本進程必須等待其釋放信號後,才可繼續執行。若申請到信號,則可繼續執行,但其它進程必須等待本進程釋放信號量後,才能再使用本信號。
保證函數的可重入性的方法:在寫函數時候儘量使用局部變量(例如寄存器、堆棧中的變量),對於要使用的全局變量要加以保護(如採取關中斷、信號量等方法),這樣構成的函數就一定是一個可重入的函數。
5、不可重入
在實時系統的設計中,經常會出現多個任務調用同一個函數的情況。如果這個函數被設計成為不可重入的函數的話,那麼不同任務調用這個函數時可能修改其他任務用到的數據,從而導致不可預料的後果。那麼什麼是可重入函數呢?
所謂可重入函數是指一個可以被多個任務調用的函數(過程),任務在調用時不必擔心數據是否會出錯。不可重入函數在實時系統設計中被視為不安全函數。
滿足下列條件的函數多數是不可重入的:
1) 函數體內使用了靜態的數據結構;
2) 函數體內調用了malloc()或者free()函數;
3) 函數體內調用了標準I/O函數。
下面舉例加以說明。
A. 可重入函數
void strcpy(char *lpszDest, char *lpszSrc) {
while(*lpszDest++=*lpszSrc++);
*dest=0;
}
B. 不可重入函數1
char cTemp;//全局變量
void SwapChar1(char *lpcX, char *lpcY) {
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//訪問了全局變量
}
C. 不可重入函數2
void SwapChar2(char *lpcX,char *lpcY) {
static char cTemp;//靜態局部變量
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//使用了靜態局部變量
}
二、SIGCHLD 語義
正如上一篇信號中說,無論一個進程是正常終止還是異常終止,都會通過系統內核向其父進程發送 SIGCHLD (17) 信號。父進程完全可以在針對 SIGCHLD (17) 信號的信號處理函數中,異步地回收子進程的殭屍,簡潔而又高效。
1、示例說明
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
void sigchld (int signum)
{
for (;;)
{
pid_t pid = waitpid (-1, NULL, WNOHANG);
if (pid == -1)
{
if (errno != ECHILD)
{
perror ("wait"), exit (1);
}
printf ("子進程都死光了\\n");
break;
}
if (!pid)
break;
printf ("%d子進程終止\\n", pid);
}
}
int main (void)
{
if (signal (SIGCHLD, sigchld) == SIG_ERR)
perror ("signal"), exit (1);
sleep (10);
pid_t pid1 = fork ();
if (pid1 == -1)
perror ("fork"), exit (1);
else if (pid1 == 0)
{
printf ("這是子進程 pid = %d", getpid ());
printf ("父進程的 ppid = %d\\n", getppid ());
}
else
{
sleep (10); //可以保證子進程先被調度
printf ("這是父進程 ppid = %d\\n", getpid ());
}
return 0;
}
在一個終端輸出結果:
這是子進程 pid = 3653父進程的 ppid = 3635
3653子進程終止
子進程都死光了
這是父進程 ppid = 3635
在另個一個終端查看
# ps -C a.out -o ppid,pid,stat,cmd
PPID PID STAT CMD
3486 3635 S+ ./a.out
2、示例解析
使用具有非阻塞特性的 waitpid 函數。在一個循環過程中回收儘可能多的殭屍。
學習編程(C語言/C++)並不難各位可以加下群466572167(資料和視頻),一起交流提升,編程不要覺得很難,雖說也有難度,但是學好了對以後的幫助是非常大。
閱讀更多 java架構社區 的文章