2013-01-20 25 views
5

首先,我知道互斥鎖通常不被認爲是同步安全的。這個問題涉及使用sigprocmask在具有異步信號和信號處理程序的多線程程序中使互斥鎖安全。使用異步信號保證互斥安全

我有一些代碼在概念上類似如下:

struct { int a, b; } gvars; 

void sigfoo_handler(int signo, siginfo_t *info, void *context) { 
    if(gvars.a == 42 || gvars.b == 13) { 
     /* run a chained signal handler */ 
    } 
} 

/* called from normal code */ 
void update_gvars(int a, int b) { 
    gvars.a = a; 
    gvars.b = b; 
} 

gvars是一個全局變量太大,適合在一個單一sig_atomic_t。它由正常代碼更新並從信號處理程序讀取。受控代碼是一個鏈式信號處理程序,因此它必須在信號處理程序上下文中運行(它可能使用infocontext)。因此,所有對gvars的訪問必須通過某種同步機制來控制。複雜的事情,該程序是多線程的,任何線程可能會收到SIGFOO

問題:通過結合sigprocmask(或pthread_sigmask)和pthread_mutex_t,是有可能保證同步,使用如下所示的代碼?

struct { int a, b; } gvars; 
pthread_mutex_t gvars_mutex; 

void sigfoo_handler(int signo, siginfo_t *info, void *context) { 
    /* Assume SIGFOO's handler does not have NODEFER set, i.e. it is automatically blocked upon entry */ 
    pthread_mutex_lock(&gvars_mutex); 
    int cond = gvars.a == 42 || gvars.b == 13; 
    pthread_mutex_unlock(&gvars_mutex); 

    if(cond) { 
     /* run a chained signal handler */ 
    } 
} 

/* called from normal code */ 
void update_gvars(int a, int b) { 
    sigset_t set, oset; 
    sigemptyset(&set); 
    sigaddset(&set, SIGFOO); 
    pthread_sigmask(SIG_BLOCK, &set, &oset); 
    pthread_mutex_lock(&gvars_mutex); 
    gvars.a = a; 
    gvars.b = b; 
    pthread_mutex_unlock(&gvars_mutex); 
    pthread_sigmask(SIG_SETMASK, &oset, NULL); 
} 

的邏輯是如下:內sigfoo_handlerSIGFOO被阻塞,因此它不能中斷pthread_mutex_lock。在update_gvarsSIGFOO不能在當前線程的pthread_sigmask重保護的關鍵區域時提出的,因此它不能中斷pthread_mutex_lock無論是。假設沒有其他信號(並且我們始終可以阻止任何其他可能有問題的信號),鎖定/解鎖應始終在當前線程上以正常,不可中斷的方式進行,並且使用鎖定/解鎖應確保其他線程不會干擾。我是對的,還是應該避免這種做法?

回答

1

你明明知道你是爲未定義行爲的領土與sig_atomic_t提及。話雖如此,我可以看到這個精確的例子不適用於現代類Unix系統的唯一方法是如果信號是用SA_NODEFER設置的。

互斥是足以確保不同的線程(包括另一線程運行信號處理程序)和sigmask將防止在這個線程遞歸互斥信號處理程序之間的正確同步。

這就是說,你在信號處理程序裏面有鎖的深水裏。一個信號處理程序可能足夠安全,但如果您有兩個信號處理程序使用不同的鎖來執行相同的技巧,則最終會發生鎖定排序死鎖。這可以通過應用進程sigmasks而不是線程sigmasks來緩解。例如,信號處理程序中的一個簡單的調試fprintf肯定會違反鎖定順序。

我會退避三舍,並重新設計我的應用程序,因爲這樣的東西在一個訊號處理器是它變得​​太複雜,太容易突破的跡象。接觸一個sig_atomic_t的信號處理程序是C標準中唯一定義的東西,因爲其他任何東西都是正確的。

+0

如果信號設置爲'SA_NODEFER',那麼我應該可以在處理程序本身中以相同的方式使用'sigprocmask',不是嗎? – nneonneo

+1

另外,由於在兩個線程中同時使用'sigprocmask'肯定是一個禁忌,所以進程的sigmask是不可能的。如果有多個信號處理程序,我懷疑處理它的最好方法是阻塞線程sigmask中的所有信號。 – nneonneo

+0

我會重新設計應用程序,如果可以的話,顯然,但有很好的理由爲什麼這樣的事情在我的情況下是必要的。 – nneonneo

1

我發現本文https://www.cs.purdue.edu/homes/rego/cs543/threads/signals.pdf,討論中的簽名的處理程序由

  1. 運行AS-不安全的代碼安全地在正常上下文代碼AS-不安全塊屏蔽掉信號(探索作爲效率較低)OR
  2. 保護的正常上下文代碼AS-不安全塊有防止AS-不安全代碼在處理程序從如果設置被輸入一個全球sig_atomic揮發性標誌(探索作爲有效)

這種方法滿足P的部分OSIX標準,指出 調用AS-不安全的功能SIG-處理器僅被視爲不安全,如果sighandler中斷的AS-不安全的功能(http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03_03:功能列表後,向下滾動到第1段)

我想你「再在這裏玩弄基本上是這種想法的更細粒度的版本,因爲你不試圖阻止

pthread_mutex_lock(&gvars_mutex); 
int cond = gvars.a == 42 || gvars.b == 13; 
pthread_mutex_unlock(&gvars_mutex); 

從SIG-處理程序運行從任何 AS-不安全的代碼,而是發生衝突只需要處理這個互斥體和這些變量的相同/相似的AS不安全的代碼。

不幸的是,POSIX似乎只有一個純代碼的信號安全概念:一個函數或者是安全的,或者是不安全的,不管它的參數如何。

然而,IMO,信號量/互斥體沒有充分的理由對任何數據操作或OS處理比那些包含在其他互斥/信號燈他們過去了,所以我覺得從信號處理程序調用sem_wait(&sem)/pthread_mutex_lock(&mx);應該如果保證從不與sem_wait/pthread_mutex_lock衝突到相同的互斥體,即使POSIX標準在技術上說它不應該是安全的(反義參數不止是歡迎詞),也是安全的。

+1

那麼,我認爲你是正確的論點或多或少是「我們在生產中使用它,沒有任何損失」:D。但是,當然,任何正確性都可能僅僅取決於運氣(例如,內核的行爲是正確的,libc的行爲是正確的,等等),而不是根據標準嚴格正確。 – nneonneo