進程間通信之POSIX信號量


POSIX信號量接口,意在解決XSI信號量接口的幾個不足之處:

  • POSIX信號量接口相比於XSI信號量接口,允許更高性能的實現。
  • POSIX信號量接口簡單易用:沒有信號量集,其中一些接口模仿了我們熟悉的文件系統操作。
  • POSIX信號量刪除時的處理更加合理。XSI信號量被刪除后,使用該信號量標識符的操作將會出錯返回,並將errno設置為EIDRM。而對於POSIX信號量,操作可以繼續正常執行,直到對該信號量的最后一個引用被釋放。

POSIX信號量有兩種形式可供選用:有名和無名。它們的區別在於,如何被創建和銷毀,其他方面則完全相同。無名信號量只存在於內存中,並且規定能夠訪問該內存的進程才能夠使用該內存中的信號量。這就意味着,無名信號量只能被這樣兩種線程使用:(1)來自同一進程的各個線程(2)來自不同進程的各個線程,但是這些進程映射了相同的內存范圍到自己的地址空間。相反,有名信號量則是通過名字訪問,因此,來自於任何進程的線程,只要知道該有名信號量的名字都可以訪問。

調用sem_open函數可以創建一個新的有名信號量,或打開一個現存的有名信號量

#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, ... /* mode_t mode, unsigned int value */ );
返回值:若成功則返回指向信號量的指針,若出錯則返回SEM_FAILED

如果使用一個現存的有名信號量,我們只需指定兩個參數:信號量名和oflag(oflag取0)。把oflag設置為O_CREAT標志時,如果指定的信號量不存在則新建一個有名信號量;如果指定的信號量已經存在,那么打開使用,無其他額外操作發生。

如果我們指定O_CREAT標志,那么就需要提供另外兩個參數:mode和value。mode用來指定誰可以訪問該信號量。它可以取打開文件時所用的權限位的取值(參考http://www.cnblogs.com/nufangrensheng/p/3502097.html中表4-5)。最終賦予信號量的訪問權限,是被調用者文件創建屏蔽字所修改過的(http://www.cnblogs.com/nufangrensheng/p/3502328.html)。然而,注意通常只有讀寫權限對我們有用,但是接口不允許在我們打開一個現存的信號量時指定打開模式(mode)。實現通常以讀寫打開信號量。

value參數用來指定信號量的初始值。它可取值為:0-SEM_VALUE_MAX。

如果我們想要確保我們在創建一個新的信號量,可以把oflag參數設置為:O_CREAT|O_EXCL。如果信號量已經存在的話,這會導致sem_open調用失敗。

為了提高移植性,我們在選擇信號量名字的時候,必須遵循一定的約定:

  • 名字的首字符必須是斜杠(/)。
  • 除首字符外,名字中不能再包含其他斜杠(/)。
  • 名字的最長長度由實現定義,不應超過_POSIX_NAME_MAX個字符。

sem_open函數返回一個信號量指針,該指針可供其他對該信號量進行操作的函數使用。使用完成后,調用sem_close函數釋放與信號量相關的資源

#include <semaphore.h>
int sem_close(sem_t *sem);
返回值:若成功則返回0,出錯返回-1

如果進程還沒有調用sem_close就已經退出,那么內核會自動關閉該進程打開的所有信號量。注意,這並不會影響信號量值的狀態——例如,如果我們增加了信號量的值,我們退出后這個值不會改變。同樣,如果我們調用了sem_close,信號量值也不會受到影響。POSIX信號量機制中並沒有如同XSI信號量中的SEM_UNDO標志。

調用sem_unlink函數來銷毀一個有名信號量

#include <semaphore.h>
int sem_unlink(const char *name);
返回值:若成功則返回0,出錯則返回-1

sem_unlink函數移除信號量的名字。如果當前沒有打開的對該信號量的引用,那么就銷毀它。否則,銷毀被推遲到最后一個打開的引用被關閉。

與XSI信號量不同,我們只能對POSIX信號量的值進行加1或減1。對信號量值減1,就類似於對一個二值信號量加鎖或請求一個與計數信號量相關的資源。

注意,POSIX信號量並沒有區分信號量類型。使用二值信號量還是計數信號量,取決於我們如果對信號進行初始化和使用。如果信號量值只能取0和1,那么它就是一個二值信號量。當一個二值信號量值為1,我們則說它未加鎖;若它的值為0,則說它已加鎖。

調用sem_wait或sem_trywait函數,請求一個信號量(對信號量值執行減1操作)。

#include <semaphore.h>
int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);
兩個函數返回值:若成功則返回0,出錯則返回-1

如果信號量計數為0,這時如果調用sem_wait函數,將會阻塞。直到成功對信號量計數減1或被一個信號中斷,sem_wait函數才會返回。我們可以使用sem_trywait函數以避免阻塞。當我們調用sem_trywait函數時,如果信號量計數為0,sem_trywait會返回-1,並將errno設置為EAGAIN。

第三種方法是可以阻塞一段有限的時間,這時我們使用sem_timedwait函數

#include <semaphore.h>
#include <time.h>

int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsptr);
返回值:若成功則返回0,出錯則返回-1

tsptr參數指定了希望等待的絕對時間。如果信號量可以被立即減1,那么超時也無所謂,即使你指定了一個已經過去的時間,試圖對信號量減1的操作也會成功。如果直到超時,還不能對信號量計數減1,那么sem_timedwait函數將會返回-1,並將errno設置為ETIMEDOUT。

調用sem_post函數對信號量值加1。這類似於對一個二值信號量解鎖或釋放一個與計數信號量有關的資源。

#include <semaphore.h>
int sem_post(sem_t *sem);
返回值:若成功則返回0,出錯則返回-1

當我們調用sem_post的時,如果此時有因為調用sem_wait或sem_timedwait而阻塞的進程,那么該進程將被喚醒,並且剛剛被sem_post加1的信號量計數緊接着又被sem_wait或sem_timedwait減1。

如果我們想要在一個單一進程內使用POSIX信號量,那么使用無名信號量會更加簡單。無名信號量只是創建和銷毀有所改變,其他完全和有名信號量一樣。我們調用sem_init函數創建一個無名信號量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
返回值:若成功則返回0,出錯返回-1

pshared參數指示我們是否要在多進程之間使用該無名信號量。如果要在多個進程之間使用,則將pshared設置為非0值。value參數指定信號量的初始值。

另外,我們需要聲明一個sem_t類型的變量,並把它的地址傳給sem_init,以便對該變量進行初始化。如果我們要在兩個進程之間使用該無名信號量,我們需要確保sem參數指向這兩個進程共享的內存范圍內。

我們可以調用sem_destroy函數來銷毀用完的無名信號量

#include <semaphore.h>
int sem_destroy(sem_t *sem);
返回值:若成功則返回0,出錯則返回-1

調用sem_destroy后我們將不能再以sem為參數調用任何信號量函數,除非我們再次使用sem_init對sem進行初始化。

我們可以調用sem_getvalue函數來獲取信號量值。

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *restrict valp);
返回值:若成功則返回0,出錯則返回-1

如果sem_getvalue執行成功,信號量的值將存入valp指向的整型變量中。但是,需要小心,我們剛讀出來的信號量值可能會改變(因為我們隨時可能會使用該信號量值)。如果不采取額外的同步機制的話,sem_getvalue函數僅僅用來調試。

實例

回憶http://www.cnblogs.com/nufangrensheng/p/3523623.html的表12-4,Single UNIX Specification並沒有定義當一個線程鎖定了一個normal類型的mutex,而另外一個線程試圖對此mutex進行解鎖會發生什么(對應於表12-4中“不占用時解鎖”欄)。但是對於error-checking類型和recursive類型的mutex,在這種情況下則會出錯。因為二值信號量可以像互斥量(mutex)一樣使用,我們可以使用信號量創建我們自己的鎖原語(primitive)來提供互斥。

假定我們要創建自己的鎖:它可以被一個線程加鎖,而被另外一個線程解鎖。我們的鎖結構可以如下:

struct slock {
    sem_t    *semp;
    char     name[_POSIX_NAME_MAX];
};

程序清單15-35顯示了一種基於信號量的互斥原語的實現。

程序清單15-35 使用POSIX信號量的互斥

#include "slock.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <error.h>

struct slock *
s_alloc()
{
    struct slock *sp;
    static int    cnt;
    
    if((sp = malloc(sizeof(struct slock))) == NULL)
        return(NULL);
    do
    {
        snprintf(sp->name, sizeof(sp->name), "/%ld.%d", (long)getpid(),
            cnt++);
        sp->semp =sem_open(sp->name, O_CREAT|O_EXCL, S_IRWXU, 1);
    }
    while((sp->semp == SEM_FAILED) && (errno == EEXIST));
    if(sp->semp == SEM_FAILED)
    {
        free(sp);
        return(NULL);
    }
    sem_unlink(sp->name);
    return(sp);
}

void
s_free(struct slock *sp)
{
    sem_close(sp->semp);
    free(sp);
}

int 
s_lock(struct slock *sp)
{
    return(sem_wait(sp->semp));
}

int
s_trylock(struct slock *sp)
{
    return(sem_trywait(sp->semp));
}

int
s_unlock(struct slock *sp)
{
    return(sem_post(sp->semp));
}

我們基於進程ID和計數counter創建一個名字。我們無需使用互斥量對counter加以保護,因為如果兩個相互競爭的線程同時調用s_alloc並且分配了相同的名字,但是使用O_EXCL標志調用sem_open時只會有一個調用成功,而另一個以EEXIST出錯返回,這時我們只需重新調用一次就行了。注意,我們在打開信號量后隨即銷毀它。這樣其他進程就不能再訪問它,而且簡化了進程結束時的清理工作。

本篇博文內容摘自《UNIX環境高級編程》(第3版),僅作個人學習記錄所用。關於本書可參考:http://www.apuebook.com/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM