信號量
信號量(Semaphore)是一種用於實現計算機資源共享的IPC機制之一,其本質是一個計數器。信號量是在多進程環境下實現資源互斥訪問或共享資源訪問的方法,可以用來保證兩個或多個關鍵代碼段不被並發調用。在進入一個關鍵代碼段之前,進程/線程必須獲取一個信號量;一旦該關鍵代碼段完成了,那么該進程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待直到第一個進程釋放信號量。
信號量有兩種應用形式:一種用於臨界資源的互斥訪問,臨界資源在同一時刻只允許一個進程使用,此時的信號量是一個二元信號量,它只控制一個資源;另一種應用於處理多個共享資源(例如多台打印機的分配),信號量在其中起到記錄空閑資源數目的作用。
Linux信號量定義
Linux多進程訪問共享資源時,需要按下列步驟進行操作:
(1)檢測控制這個資源的信號量的值。
(2)如果信號量是正數,就可以使用這個資源。進程將信號量的值減一,表示當前進程占用了一份資源。
(3)如果信號量是0,那么進程進入睡眠狀態,直到信號量的值重新大於0時被喚醒,轉入第一步操作。
上述過程也被稱為PV操作。為了正確實現信號量機制,檢測和增減信號量的值都應該是原語操作,因此信號量一般是在內核中實現的。
在信號量的實際應用中,是不能單獨定義一個信號量的,只能定義一個信號量集,其中包含一組信號量。同一信號量集中的信號量使用同一個引用ID,這樣的設置是為了多個資源或同步操作的需要。每個信號量集都有一個與之對應的結構,其中記錄了信號量集的各種信息,該結構的定義如下:
#include <sys/sem.h> struct semid_ds { struct ipc_perm sem_perm; //指向與信號量集相對應的ipc_perm結構的指針 struct sem *sem_base; //指向這個集合中第一個信號量的指針 ushort sem_nsems; //集合中信號量的數量 time_t sem_otime; //最近一次調用semop函數的時間 time_t sem_ctime; //最近一次改變的時間 };
semid_ds結構中的sem結構記錄了單一信號量的一些信息:
struct sem { ushort semval; //信號量的值 pid_t sempid; //最近一次執行操作的進程的進程號 ushort semncnt; //等待信號值增長,即等待可利用資源出現的進程數 ushort semzcnt; //等待信號值減少,即等待全部資源可被獨占的進程數 };
信號量操作
創建或打開
semget函數用於創建或打開一個信號量集,其中的參數key和參數semflg的取值和用法與消息隊列的msgget函數相同。nsems參數用於指出信號量集中創建的信號量數量,如果是打開一個已存在的信號量集,該參數被忽略。如果調用成功則返回信號量集標識符,否則返回-1。
#include <sys/sem.h> #include <sys/ipc.h> #include <sys/types.h> int semget(key_t key, int nsems, int semflg);
當一個新的信號量集被創建時,與之相關聯的semid_ds被初始化:
·ipc_perm被初始化,其中mode的設置會按照semflg的要求進行。
·em_otime被置為0。
·sem_ctime被設置為當前時間。
·sem_nsems被置為參數nsems的值。
操作信號量集
semop函數用於對信號量集進行操作。
#include <sys/ipc.h> #include <sys/sem.h> #include <sys/types.h> int semop(int semid, struct sembuf *sops, unsigned nsops);
參數semid是一個通過semget函數返回的信號量集標識符;參數nsops標明了參數sops所指向數組中的元素個數;參數sops為sembuf結構的數組,其中每個元素表示一個操作,semop是原子操作,因此一旦函數執行就將執行數組中的全部操作。
結構sembuf用來說明semop函數要對信號量集執行的操作:
struct sembuf { unsigned short sem_num; short sem_op; short sem_flg; };
sembuf結構中,sem_num是相對應的信號量集中的某一個資源(即指定將要操作的信號量),所以其值是一個從0到響應的信號量集的資源總數(ipc_perm.sem_nsems)之間的整數。sem_op指明要執行的操作,sem_flg說明semop的行為,其值及所對應的操作如下:
·sem_op>0:表示進程對資源使用完畢后釋放的資源數量,並將sem_op的值加到信號量的值上。
·sem_op = 0:進程阻塞直到信號量的相應值為0,當信號量已經為0,函數立即返回。如果信號量的值不為0,則依據sem_flg的IPC_NOWAIT位決定函數動作。sem_flg指定IPC_NOWAIT,則semop函數出錯返回,若沒有指定則將該信號量的semncnt加1,然后進程掛起直到下述情況發生:信號量的值為0,將信號量的semzcnt的值減1,函數semop成功返回;此信號量被刪除(只有超級用戶或創建用戶進程才有此權限),函數semop出錯返回;進程捕捉到信號,並從信號處理函數返回,此情況下將此信號量的semncnt減1,函數semop出錯返回。
·sem_op<0:請求sem_op的絕對值資源。如果相應的資源數可以滿足請求,則將該信號量減去sem_op的絕對值,函數成功返回。當相應的資源數不能滿足請求時,則這個操作與sem_flg有關。若sem_flg指定IPC_NOWAIT,則semop函數出錯返回,若沒有指定則將該信號量的semncnt加1,然后進程掛起直到下述情況發生:當相應的資源數可以滿足請求,則該信號的值減去sem_op的絕對值,函數成功返回;此信號量被刪除,函數semop出錯返回;進程捕捉到信號,並從信號處理函數返回,此情況下將此信號量的semncnt減1,函數semop出錯返回。
以下代碼是使用semop函數的示例:
#include <iostream> #include <cstdlib> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int main(int argc, char *argv[]) { //創建信號量集 int nsems = 1; //設置信號量集的信號量數量為1 int sem_id = semget(IPC_PRIVATE, nsems, 0666); if (sem_id < 0) { std::cerr << "創建信號量集失敗\n"; exit(1); } std::cout << "成功創建信號量集,標識符為 " << sem_id << "\n"; //定義信號量集操作 struct sem_buf buf; buf.sem_num = 0; //設置當前可用資源數為0 buf.sem_op = 1; //釋放資源數 buf.sem_flg = IPC_NOWAIT; //對信號量集進行釋放資源操作 if (semop(sem_id, &buf, nsems) < 0) { std::cerr << "semop\n"; exit(1); } //查看系統IPC狀態 system("ipcs -s"); //刪除信號量集 if (semctl(sem_id, 0, IPC_RMID) < 0) { std::cerr << "刪除信號量集失敗\n"; exit(1); } exit(0); }
控制信號量集
semctl是信號量集控制函數,用於控制信號量集,如設置單個信號量的值、刪除信號量集等。其標准調用格式如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semmum, int cmd, ...);
參數semid是信號量集的標識符,參數semmum用於指定信號量集中的某一個信號量(類似於數組下標,從0開始),參數cmd則用於指定具體的操作。
參數值 | 說明 |
---|---|
SETVAL | 設置單個信號量的值 |
GETALL | 返回信號量集中所有信號量的值 |
SETALL | 設置信號量集中所有信號量的值 |
IPC_STAT | 放置與信號量集相連的semid_ds結構當前值於arg.buf指定的緩沖區 |
IPC_SET | 用arg.buf指定結構值替代與信號量集合相連的semid_ds結構的值 |
GETVAL | 返回單個信號量的值 |
GETPID | 返回最后一個操作該信號量集的進程ID |
GETNCNT | 返回semncnt的值 |
GETZCNT | 返回semzcnt的值 |
IPC_RMID | 刪除指定的信號量集 |
執行IPC_SET、IPC_RMID命令的進程只能是信號量集的創建進程、擁有者進程或特權進程,執行其他命令的進程必須擁有信號量集的讀取或更新權限。
根據實際cmd參數的具體內容,semctl可能擁有第4個參數arg,這是一個共用體(或稱聯合體),其中的val用於semctl中cmd值為SETVAL時,指明要設置的信號量值;buf用於IPC_STAT/IPC_SET,表示存放信號量集合數據結構的緩沖區;array用於GETALL/SETALL,存放所獲得的或要設置的信號量集中所有信號量的值。結構定義如下:
union semum { int val; struct semid_ds *buf; unsigned short array; };
如果semctl函數調用成功,則返回值大於等於0(當semctl操作為GET操作時返回相應的值,其余返回0);如果調用失敗則返回-1,並設置錯誤變量errno為對應的值。
喜歡這篇文章?歡迎打賞~~