05.05 學習Unix——可重入函數和SIGCHLD語義

一、可重入函數

參與信號處理的函數必須是可重入函數。

1、何為重入?

假設進程的住控制流程此刻正在調用 foo 函數,就在 foo 函數剛執行到一半的時候,內核向進程遞送了信號 a;假設進程對信號 a 做了捕獲,那麼此時流程將轉入信號 a 的處理函數 siga,而 siga 函數在執行過程中也調用了 foo 函數。於是 foo 函數中的代碼又被執行,剛執行到一半,內核又遞送給了信號 b。假設進程對信號 b 也做了捕獲,並用 sigb 函數來處理,而在 sigb 函數中同樣調用了 foo 函數。

注意,此時的 foo 函數已被重入 3 次,假設該函數中包含了對全局變量、靜態變量、磁盤文件等共享型資源的訪問,其結果將會如何?數據的一致性和安全性如何得到保證?

學習Unix——可重入函數和SIGCHLD語義

2、編寫可重入函數

編寫參與信號處理的函數(比如前例中的 foo 函數)時必須要注意,不要對進程在中斷時所做的事情做任何假設。尤其是在對全局變量、靜態局部變量、磁盤文件這些帶有共享特性的對象做寫操作的時候,必須要謹慎。當然,如果能夠不碰或者只讀這些全部對象,那當然再好也沒有。

可重入函數是指可以安全地調用自身(從信號處理中或從其它線程中)的函數。為了使函數可重入,函數決不能操作靜態數據,只訪問在棧裡分配的數據和調用者提供的數據,同時也不得調用任何不可重入的函數

注意,編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(即P、V操作)等手段對其加以保護

若對所使用的全局變量不加以保護,則此函數就不具有可重入性,即當多個進程調用此函數時,很有可能使有關全局變量變為不可知狀態。

3、可重入函數

Single UNIX Specification 說明了在信號處理程序中保證調用安全的函數。這些函數時可重入的並被稱為異步信號安全的。除了可重入之外,在信號處理操作期間,它會阻塞任何會引起不一致的信號發送

下圖列出了異步信號安全的函數,即可重入函數:

學習Unix——可重入函數和SIGCHLD語義

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 函數。在一個循環過程中回收儘可能多的殭屍。

學習Unix——可重入函數和SIGCHLD語義

學習編程(C語言/C++)並不難各位可以加下群466572167(資料和視頻),一起交流提升,編程不要覺得很難,雖說也有難度,但是學好了對以後的幫助是非常大。


分享到:


相關文章: