作者:楊碩,華清遠見嵌入式學院講師。
一、信號燈簡介:
Linux支持系統(tǒng)5的信號燈(semaphore),是一種進程間通信的方式,只不過它和管道、FIFO或者共享內存等不一樣,信號燈主要用于同步或者互斥對共享資源的訪問,它的發(fā)明來源于火車運行系統(tǒng)中的“信號燈”,利用信號燈可以實現 “PV操作”這種進程間同步機制。P操作是獲得資源,將信號燈的值減1,如果結果不為負則執(zhí)行完畢,進程獲得資源,否則進程睡眠以等待資源別的進程釋放資源;V操作則是釋放資源,給信號燈的值加1,釋放一個因執(zhí)行P操作而等待的進程。
二、信號燈的兩種類型
1、二值信號燈:
最簡單的信號燈形式,信號燈的值只能取0或1,類似于互斥鎖。
雖然二值信號燈能夠實現互斥鎖的功能,但兩者的關注內容不同。信號燈強調共享資源,只要共享資源可用,其他進程同樣可以修改信號燈的值;互斥鎖更強調進程,占用資源的進程使用完資源后,必須由進程本身來解鎖。
2、 計數信號燈:
信號燈的值可以取任意非負值(當然受內核本身的約束),用來統(tǒng)計資源,其值就代表可用資源的個數。
三、Linux下對信號燈的操作
1、 打開或創(chuàng)建信號燈
對應的系統(tǒng)調用:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int sem*);
第一個參數key是一個鍵值,信號燈集的描述符就由系統(tǒng)范圍內唯一的一個鍵值生成。
key可以由ftok函數生產:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(conST char *pathname, int proj_id);
ftok返回與系統(tǒng)中的路徑pathname相對應的一個鍵值
nsems是信號燈集中信號燈的個數,其最大值取決于具體的系統(tǒng),如果是0,則代表訪問已存在的信號燈集。
sem*是一些標志位,它是IPC_CREAT、IPC_EXCL、IPC_NOWAIT三者與訪問權限或的結果,訪問權限一般都是0600,代表只有信號燈集的屬主才對信號燈集有讀寫的權限。
semget()如果執(zhí)行成功,返回與key對應的信號燈集描述字(非負整數,存在于內存之中),失敗返回-1,并將錯誤碼置于errno全局變量中。
2、操作信號燈
linux可以增加或減小信號燈的值,相應于對共享資源的釋放和占有。
對應的系統(tǒng)調用:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
semop系統(tǒng)調用可以實現對由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_*; /* 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_*指定為IPC_NOWAIT,則調用進程立即返回。
● sem_op > 0,代表進程要釋放sem_op數量的共享資源。也就是V操作。
sem_*可取0,IPC_NOWAIT以及SEM_UNDO兩個標志。
● 0代表阻塞調用
● IPC_NOWAIT代表非阻塞調用
● 如果設置了SEM_UNDO標志,那么在進程結束時,相應的操作將被取消,這是比較重要的一個標志位。如果設置了該標志位,那么在進程沒有釋放共享資源就退出時,內核將代為釋放。如果為一個信號燈設置了該標志,內核都要分配一個 sem_undo結構來記錄它,為的是確保以后資源能夠安全釋放。事實上,如果進程退出了,那么它所占用就釋放了,但信號燈值卻沒有改變,此時,信號燈值反映的已經不是資源占有的實際情況,在這種情況下,問題的解決就靠內核來完成。這有點像僵尸進程,進程雖然退出了,資源也都釋放了,但內核進程表中仍然有它的記錄,此時就需要父進程調用waitpid來解決問題了。
semop調用成功返回0,失敗返回-1,并將錯誤碼置于errno全局變量中。
semop可以同時操作多個信號燈,在實際應用中,對應多種資源的申請或釋放。semop保證操作的原子性,這一點尤為重要。尤其對于多種資源的申請來說,要么一次性獲得所有資源,要么放棄申請,要么在不占有任何資源情況下繼續(xù)等待,這樣,一方面避免了資源的浪費;另一方面,避免了進程之間由于申請共享資源而造成死鎖。
3、 獲得或設置信號燈屬性:
對應的系統(tǒng)調用:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
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_* = 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_* = 0;
semop(sem_id, &op, 1);
}
“本文由華清遠見http://www.embedu.org/index.htm提供”
華清遠見