1、信號量的基本概念
信號量是一個計數器,常用於處理進程或線程的同步問題,特別是對臨界資源的同步訪問。
臨界資源可以簡單的理解為在某一時刻只能由一個進程或線程進行操作的資源,這里的資源
可以是一段代碼、一個變量或某種硬件資源。信號量的值大於或等於0時表示可供並發進程使用的
資源實體數;小於0時代表正在等待使用臨界資源的進程數。
注意:這里的信號量跟信號是沒有關系的。
與消息隊列類似,linux內核也為每個信號量維護了一個semid_ds 數據結構實例,
在文件/usr/include/linux/sem.h中
struct semid_ds {
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
__kernel_time_t sem_otime; /* last semop time */
__kernel_time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned short sem_nsems; /* no. of semaphores in array */
};
2、信號量的創建與使用
linux下使用semget創建或打開信號量集,
int semget(key_t key, int nsems, int semflg);
該函數執行成功則返回一個信號量集的標識符,失敗返回-1。返回的參數key是由ftok得到的鍵值;
第二個參數nsems指明要創建的信號量集包含的信號量個數。如果只是打開信號量,把nsems設置為0即可。該參數只在創建信號量集時有效。
第三個參數semflg為操作標識,可取如下值:
0:取信號量集標識符,若不存在則函數會報錯
IPC_CREAT:當semflg&IPC_CREAT為真時,如果內核中不存在鍵值與key相等的信號量集,則新建一個信號量集;如果存在這樣的信號量集,返回此信號量集的標識符
IPC_CREAT|IPC_EXCL:如果內核中不存在鍵值與key相等的信號量集,則新建一個消息隊列;如果存在這樣的信號量集則報錯
上述semflg參數為模式標志參數,使用時需要與IPC對象存取權限(如0600)進行|運算來確定信號量集的存取權限
錯誤代碼:
EACCESS:沒有權限
EEXIST:信號量集已經存在,無法創建
EIDRM:信號量集已經刪除
ENOENT:信號量集不存在,同時semflg沒有設置IPC_CREAT標志
ENOMEM:沒有足夠的內存創建新的信號量集
ENOSPC:超出限制
如果用semget創建了一個新的信號量集對象時,則semid_ds結構成員變量的值設置如下:
sem_otime設置為0。
sem_ctime設置為當前時間。
msg_qbytes設成系統的限制值。
sem_nsems設置為nsems參數的數值。
semflg的讀寫權限寫入sem_perm.mode中。
sem_perm結構的uid和cuid成員被設置成當前進程的有效用戶ID,gid和cuid成員被設置成當前進程的有效組ID。
--------------------------------------------------
3、信號量的操作
信號量的值與相應資源的使用情況有關,當它的值大於0時,表示當前可用資源的數量,當他的值小於0時,其絕對值表示等待
使用這個資源的進程個數。信號量的值僅能由PV操作來改變。
在linux下,pv操作通過調用函數semop實現。該函數定義在頭文件sys/sem.h中,原型:
int semop(int semid, struct sembuf *sops, unsigned nsops);
對信號量集標識符為semid中的一個或多個信號量進行P操作或V操作
semid:信號量集標識符
sops:指向進行操作的信號量集結構體數組的首地址,此結構的具體說明如下:
struct sembuf {
short semnum; /*信號量集合中的信號量編號,0代表第1個信號量*/
short val;/*若val>0進行V操作信號量值加val,表示進程釋放控制的資源 */
/*若val<0進行P操作信號量值減val,若(semval-val)<0(semval為該信號量值),則調用進程阻塞,直到資源可用;若設置IPC_NOWAIT不會睡眠,進程直接返回EAGAIN錯誤*/
/*若val==0時阻塞等待信號量為0,調用進程進入睡眠狀態,直到信號值為0;若設置IPC_NOWAIT,進程不會睡眠,直接返回EAGAIN錯誤*/
short flag; /*0 設置信號量的默認操作*/
/*IPC_NOWAIT設置信號量操作不等待*/
/*SEM_UNDO 選項會讓內核記錄一個與調用進程相關的UNDO記錄,如果該進程崩潰,則根據這個進程的UNDO記錄自動恢復相應信號量的計數值*/
};
nsops:進行操作信號量的個數,即sops結構變量的個數,需大於或等於1。最常見設置此值等於1,只完成對一個信號量的操作
錯誤代碼:
E2BIG:一次對信號量個數的操作超過了系統限制
EACCESS:權限不夠
EAGAIN:使用了IPC_NOWAIT,但操作不能繼續進行
EFAULT:sops指向的地址無效
EIDRM:信號量集已經刪除
sops為指向sembuf數組,定義所要進行的操作序列。下面是信號量操作舉例。
struct sembuf sem_get={0,-1,IPC_NOWAIT}; /*將信號量對象中序號為0的信號量減1*/
struct sembuf sem_get={0,1,IPC_NOWAIT}; /*將信號量對象中序號為0的信號量加1*/
struct sembuf sem_get={0,0,0}; /*進程被阻塞,直到對應的信號量值為0*/
flag一般為0,若flag包含IPC_NOWAIT,則該操作為非阻塞操作。若flag包含SEM_UNDO,則當進程退出的時候會還原該進程的信號量操作,這個標志在某些情況下是很有用的,
比如某進程做了P操作得到資源,但還沒來得及做V操作時就異常退出了,此時,其他進程就只能都阻塞在P操作上,於是造成了死鎖。若采取SEM_UNDO標志,就可以避免因為進程異常退出而造成的死鎖。
4、semctl (得到一個信號量集標識符或創建一個信號量集對象)
int semctl(int semid, int semnum, int cmd, union semun arg)
semid:信號量集標識符
semnum:信號量集數組上的下標,表示某一個信號量
cmd:
命令 |
解 釋 |
IPC_STAT |
從信號量集上檢索semid_ds結構,並存到semun聯合體參數的成員buf的地址中 |
IPC_SET |
設置一個信號量集合的semid_ds結構中ipc_perm域的值,並從semun的buf中取出值 |
IPC_RMID |
從內核中刪除信號量集合 |
GETALL |
從信號量集合中獲得所有信號量的值,並把其整數值存到semun聯合體成員的一個指針數組中 |
GETNCNT |
返回當前等待資源的進程個數 |
GETPID |
返回最后一個執行系統調用semop()進程的PID |
GETVAL |
返回信號量集合內單個信號量的值 |
GETZCNT |
返回當前等待100%資源利用的進程個數 |
SETALL |
與GETALL正好相反 |
SETVAL |
用聯合體中val成員的值設置信號量集合中單個信號量的值 |
arg:
union semun {
short val; /*SETVAL用的值*/
struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds結構*/
unsigned short* array; /*SETALL、GETALL用的數組值*/
struct seminfo *buf; /*為控制IPC_INFO提供的緩存*/
} arg;
5、信號量的應用實例:
server創建一個信號量集,並對信號量循環減1,相當於分配資源。
client執行時jian'cha信號量,如果其值大於0代表有資源可用,繼續執行,如果小於等於0代表資源已經分配完畢,進程client推出。
server:
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdlib.h>
#define SIZE 5 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) */ }; int main() { key_t key; int semid; struct sembuf sbuf = {0, -1, IPC_NOWAIT}; union semun semopts; if ((key = ftok(".", 11)) == -1) { perror("ftok error:"); exit(1); } semid = semget(key, 1, IPC_CREAT|0660); semopts.val = SIZE; //第二個參數,信號量集數組上的下標,表示某一個信號量
if ( semctl(semid, 0, SETVAL, semopts) == -1 ) { perror("semctl error:"); exit(1); } while(1) { //第二個參數,指向進行操作的信號量集結構體數組的首地址 //第三個參數:進行操作信號量的個數,即sops結構變量的個數,需大於或等於1。最常見設置此值等於1,只完成對一個信號量的操作
if (semop(semid, &sbuf, 1) == -1) { perror("semop error:"); exit(1); } sleep(3); } return 0; }
client:
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdlib.h>
#define SIZE 5 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) */ }; int main() { key_t key; int semid; int semval; if ((key = ftok(".", 11)) == -1) { perror("ftok error:"); exit(1); } semid = semget(key, 1, IPC_CREAT|0660); while(1) { if ( (semval = semctl(semid, 0, GETVAL, 0)) == -1 ) { perror("semctl error:"); exit(1); } if (semval > 0) { printf("still have %d resources can be used\n", semval); } else { printf("no more resources can be use\n"); } sleep(3); } return 0; }