Linux下訊號燈的使用

helloxchen發表於2010-11-26

作者:楊碩,講師。

一、訊號燈簡介:

Linux支援系統5的訊號燈(semaphore),是一種程式間通訊的方式,只不過它和管道、FIFO或者共享記憶體等不一樣,訊號燈主要用於同步或者互斥對共享資源的訪問,它的發明來源於火車執行系統中的“訊號燈”,利用訊號燈可以實現 “PV操作”這種程式間同步機制。P操作是獲得資源,將訊號燈的值減1,如果結果不為負則執行完畢,程式獲得資源,否則程式睡眠以等待資源別的程式釋放資源;V操作則是釋放資源,給訊號燈的值加1,釋放一個因執行P操作而等待的程式。

二、訊號燈的兩種型別

1、二值訊號燈:

最簡單的訊號燈形式,訊號燈的值只能取0或1,類似於互斥鎖。

雖然二值訊號燈能夠實現互斥鎖的功能,但兩者的關注內容不同。訊號燈強調共享資源,只要共享資源可用,其他程式同樣可以修改訊號燈的值;互斥鎖更強調程式,佔用資源的程式使用完資源後,必須由程式本身來解鎖。

2、 計數訊號燈:

訊號燈的值可以取任意非負值(當然受核心本身的約束),用來統計資源,其值就代表可用資源的個數。

三、Linux下對訊號燈的操作

1、 開啟或建立訊號燈

對應的系統呼叫:
#include
#include
#include

int semget(key_t key, int nsems, int semflg);

第一個引數key是一個鍵值,訊號燈集的描述符就由系統範圍內唯一的一個鍵值生成。

key可以由ftok函式生產:

#include
#include

key_t ftok(const char *pathname, int proj_id);

ftok返回與系統中的路徑pathname相對應的一個鍵值

nsems是訊號燈集中訊號燈的個數,其最大值取決於具體的系統,如果是0,則代表訪問已存在的訊號燈集。

semflg是一些標誌位,它是IPC_CREAT、IPC_EXCL、IPC_NOWAIT三者與訪問許可權或的結果,訪問許可權一般都是0600,代表只有訊號燈集的屬主才對訊號燈集有讀寫的許可權。

semget()如果執行成功,返回與key對應的訊號燈集描述字(非負整數,存在於記憶體之中),失敗返回-1,並將錯誤碼置於errno全域性變數中。

2、操作訊號燈

linux可以增加或減小訊號燈的值,相應於對共享資源的釋放和佔有。

對應的系統呼叫:
#include
#include
#include

int semop(int semid, struct sembuf *sops, unsigned nsops);
semop系統呼叫可以實現對由semid標誌的訊號燈集中的某一個指定訊號燈的一系列操作。
semid即是semget返回的訊號燈描述字。
sops是指向結構體sembuf的指標,可以是這種型別的結構體陣列的頭指標,陣列的每一個sembuf結構都刻畫一個在特定訊號燈上的操作。
nsops為sops指向陣列的大小(有幾個sembuf結構體)。

sembuf結構體定義如下:

struct sembuf
{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};

sem_num對應訊號燈集中的訊號燈,0代表第一個訊號燈。

sem_op的值決定了對sem_num指定的訊號燈的三種不同操作:

● sem_op = 0,呼叫者阻塞等待直到訊號燈的值等於0時返回。可以用來測試共享資源是否已用完。
● sem_op > 0,代表程式要申請-sem_op個共享資源。
如果訊號燈值sem_val > abs(sem_op),則sem_val = sem_val-abs(sem_op);
否則呼叫程式睡眠直到sem_val>=abs(sem_op)。當然如果sem_flg指定為IPC_NOWAIT,則呼叫程式立即返回。
● sem_op > 0,代表程式要釋放sem_op數量的共享資源。也就是V操作。
sem_flg可取0,IPC_NOWAIT以及SEM_UNDO兩個標誌。
● 0代表阻塞呼叫
● IPC_NOWAIT代表非阻塞呼叫
● 如果設定了SEM_UNDO標誌,那麼在程式結束時,相應的操作將被取消,這是比較重要的一個標誌位。如果設定了該標誌位,那麼在程式沒有釋放共享資源就退出時,核心將代為釋放。如果為一個訊號燈設定了該標誌,核心都要分配一個 sem_undo結構來記錄它,為的是確保以後資源能夠安全釋放。事實上,如果程式退出了,那麼它所佔用就釋放了,但訊號燈值卻沒有改變,此時,訊號燈值反映的已經不是資源佔有的實際情況,在這種情況下,問題的解決就靠核心來完成。這有點像殭屍程式,程式雖然退出了,資源也都釋放了,但核心程式表中仍然有它的記錄,此時就需要父程式呼叫waitpid來解決問題了。

semop呼叫成功返回0,失敗返回-1,並將錯誤碼置於errno全域性變數中。

semop可以同時操作多個訊號燈,在實際應用中,對應多種資源的申請或釋放。semop保證操作的原子性,這一點尤為重要。尤其對於多種資源的申請來說,要麼一次性獲得所有資源,要麼放棄申請,要麼在不佔有任何資源情況下繼續等待,這樣,一方面避免了資源的浪費;另一方面,避免了程式之間由於申請共享資源而造成死鎖。

3、 獲得或設定訊號燈屬性:

對應的系統呼叫:
#include
#include
#include

int semctl(int semid, int semnum, int cmd, union semun arg);

semctl透過具體的cmd操作由semid標誌的訊號燈集上的由semnum指定的訊號燈。

常用的cmd有一下幾個:
● IPC_STAT 獲取訊號燈資訊,資訊由arg.buf返回;
● GETVAL 返回semnum所代表訊號燈的值;
● SETVAL 設定semnum所代表訊號燈的值為arg.val;
● IPC_RMID 刪除semnum所代表的訊號燈

使用者需要自己定義聯合體semun如下:

union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
};

semctl呼叫成功返回0,失敗返回-1,並將錯誤碼置於errno全域性變數中。

四、利用訊號燈實現PV操作

1、P操作:申請資源

這裡我們封裝一個函式down():
/*
* function: ask for resource, P operation
* parameter: sem_id : identifier of a semaphore set;
sem_num : semaphore number
* return value: none
*/
void down(int sem_id, int sem_num)
{
struct sembuf op;

op.sem_num = sem_num;
op.sem_op = -1;
op.sem_flg = 0;
semop(sem_id, &op, 1);
}

2、V操作:釋放資源

這裡我們封裝一個函式up():
/*
* function: free resource, V operation
* parameter: sem_id : identifier of a semaphore set;
sem_num : semaphore number
* return value: none
*/
void up(int sem_id, int sem_num)
{
struct sembuf op;

op.sem_num = sem_num;
op.sem_op = 1;
op.sem_flg = 0;
semop(sem_id, &op, 1);
}

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1042129/,如需轉載,請註明出處,否則將追究法律責任。

相關文章