System V 信號量在內核中維護,其中包括二值信號量 、計數信號量、計數信號量集。
二值信號量 : 其值只有0、1 兩種選擇,0表示資源被鎖,1表示資源可用;
計數信號量:其值在0 和某個限定值之間,不限定資源數只在0 1 之間;
計數信號量集 :多個信號量的集合組成信號量集
內核維護的信號量集結構信息如下:定義在頭文件<sys/sem.h>
struct semid_ds { struct ipc_perm sem_perm; struct sem *sem_base; ushort sem_nsems; time_t sem_otime; time_t sem_ctime; };
其中ipc_perm 結構是內核給每個進程間通信對象維護的一個信息結構,其成員包含所有者用戶id,所有者組id、創建者及其組id,以及訪問模式等;semid_ds結構體中的sem結構是內核用於維護某個給定信號量的一組值的內部結構,其結構定義:
struct sem { int semval; /* current value */ int sempid; /* pid of last operation */ struct list_head sem_pending; /* pending single-sop operations */ };
其中senval變量代表當前信號量的值,sempid 為最后一個成功操作該信號量的進程id,該結構體在內核以雙向鏈表進行 維護
semid_ds結構體中的sem_nsems成員代表該信號量標示符的信號量個數
主要函數介紹:
創建一個信號量或訪問一個已經存在的信號量集。
int semget(key_t key, int nsems, int semflg);
該函數執行成功返回信號量標示符,失敗返回-1
參數key是通過調用ftok函數得到的鍵值,nsems代表創建信號量的個數,如果只是訪問而不創建則可以指定該參數為0,我們一旦創建了該信號量,就不能更改其信號量個數,只要你不刪除該信號量,你就是重新調用該函數創建該鍵值的信號量,該函數只是返回以前創建的值,不會重新創建;
semflg 指定該信號量的讀寫權限,當創建信號量時不許加IPC_CREAT ,若指定IPC_CREAT |IPC_EXCL則創建是存在該信號量,創建失敗。
通過semget函數創建一個信號量集程序如下:(semsemget.c)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/sem.h> 5 #include <sys/ipc.h> 6 #define SEM_R 0400 //用戶(屬主)讀 7 #define SEM_A 0200 //用戶(屬主)寫 8 #define SVSEM_MODE (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6) 9 10 int main(int argc,char *argv[]) 11 { 12 int c,oflag,semid,nsems; 13 oflag = SVSEM_MODE | IPC_CREAT; //設置創建模式 14 //根據命令行參數e判斷是否制定了IPC_EXCL模式 15 while((c = getopt(argc,argv,"e"))!= -1) 16 { 17 switch(c) 18 { 19 case 'e': 20 oflag |= IPC_EXCL; 21 break; 22 } 23 } 24 //判斷命令行參數是否合法 25 if (optind != argc -2) 26 { 27 printf("usage: semcreate [-e] <pathname> <nsems>"); 28 exit(0); 29 } 30 //獲取信號量集合中的信號量個數 31 nsems = atoi(argv[optind+1]); 32 //創建信號量,通過ftok函數創建一個key,返回信號量 標識符 33 semid = semget(ftok(argv[optind],0),nsems,oflag); 34 exit(0); 35 }
打開一個信號量集后,對其中一個或多個信號量的操作。
int semop(int semid, struct sembuf *sops, unsigned nsops);
該函數執行成功返回0,失敗返回-1;
第一個參數semid 為信號量標示符;nops為第二個參數的操作數組的個數,第二個參數sops為一個結構體數組指針,結構體定義在sys/sem.h中,結構體如下
struct sembuf { unsigned short sem_num; /* semaphore index in array */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ };
sem_num 操作信號的下標,其值可以為0 到nops
sem_flg為該信號操作的標志:其值可以為0、IPC_NOWAIT 、 SEM_UNDO
0 在對信號量的操作不能執行的情況下,該操作阻塞到可以執行為止;
IPC_NOWAIT 在對信號量的操作不能執行的情況下,該操作立即返回;
SEM_UNDO當操作的進程推出后,該進程對sem進行的操作將被取消;
sem_op取值 >0 則信號量加上它的值,等價於進程釋放信號量控制的資源
sem_op取值 =0若沒有設置IPC_NOWAIT, 那么調用進程將進入睡眠狀態,直到信號量的值為0,否則進程直接返回
sem_op取值 <0則信號量加上它的值,等價於進程申請信號量控制的資源,若進程設置IPC_NOWAIT則進程再沒有可用資源情況下,進程阻塞,否則直接返回。
采用setmop函數對一個信號量執行操作程序如下:(semop.c)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/sem.h> 5 #include <sys/ipc.h> 6 7 int main(int argc,char *argv[]) 8 { 9 int c,i,flag,semid,nops; 10 struct sembuf *ptr; 11 flag = 0; 12 //根據命令行參數設置操作模式 13 while( ( c = getopt(argc,argv,"nu")) != -1) 14 { 15 switch(c) 16 { 17 case 'n': 18 flag |= IPC_NOWAIT; //非阻塞 19 break; 20 case 'u': 21 flag |= SEM_UNDO; //不可恢復 22 break; 23 } 24 } 25 if(argc - optind < 2) 26 { 27 printf("usage: semops [-n] [-u] <pathname> operation..."); 28 exit(0); 29 } 30 //打開一個已經存在的信號量集合 31 if((semid = semget(ftok(argv[optind],0),0,0)) == -1) 32 { 33 perror("semget() error"); 34 exit(-1); 35 } 36 optind++; //指向當前第一個信號量的位置 37 nops = argc - optind; //信號量個數 38 ptr = calloc(nops,sizeof(struct sembuf)); 39 for(i=0;i<nops;++i) 40 { 41 ptr[i].sem_num = i; //信號量變換 42 ptr[i].sem_op = atoi(argv[optind+i]); //設置信號量的值 43 ptr[i].sem_flg = flag; //設置操作模式 44 } 45 //對信號量執行操作 46 if(semop(semid,ptr,nops) == -1) 47 { 48 perror("semop() error"); 49 exit(-1); 50 } 51 exit(0); 52 }
對信號量執行各種控制操作。
int semctl(int semid, int semnum, int cmd, ...);
該函數執行成功返回非負值,失敗返回-1
參數semid為信號集的標識符,參數 semnum標識一個特定信號,該參數僅用於 SETVAL、GETVAL、GETPID命令
cmd控制類型,...說明函數參數是可選的,通過該共用體變量semun選擇操作參數,各字段如下:
union semun { int val; /* value for SETVAL */ struct semid_ds __user *buf; /* buffer for IPC_STAT & IPC_SET */ unsigned short __user *array; /* array for GETALL & SETALL */ struct seminfo __user *__buf; /* buffer for IPC_INFO */ void __user *__pad; };
IPC_STAT讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中。
IPC_SET設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。
IPC_RMID將信號量集從系統中刪除
GETALL用於讀取信號量集中的所有信號量的值,存於semnu的array中
SETALL 設置所指定的信號量集的每個成員semval的值
GETPID返回最后一個執行semop操作的進程的PID。
LSETVAL把的val數據成員設置為當前資源數
GETVAL把semval中的當前值作為函數的返回,即現有的資源數,返回值為非負數。
調用semctl函數設置信號量的值程序如下(semsetvalues.c):
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/sem.h> 5 #include <sys/ipc.h> 6 7 //定義信號量操作共用體結構 8 union semun 9 { 10 int val; 11 struct semid_ds *buf; 12 unsigned short *array; 13 }; 14 15 int main(int argc,char *argv[]) 16 { 17 int semid,nsems,i; 18 struct semid_ds seminfo; 19 unsigned short *ptr; 20 union semun arg; 21 if(argc < 2) 22 { 23 printf("usage: semsetvalues <pathname>[values ...]"); 24 exit(0); 25 } 26 //打開已經存在的信號量集合 27 semid = semget(ftok(argv[1],0),0,0); 28 arg.buf = &seminfo; 29 //獲取信號量集的相關信息 30 semctl(semid,0,IPC_STAT,arg); 31 nsems = arg.buf->sem_nsems; //信號量的個數 32 if(argc != nsems + 2 ) 33 { 34 printf("%s semaphores in set,%d values specified",nsems,argc-2); 35 exit(0); 36 } 37 //分配信號量 38 ptr = calloc(nsems,sizeof(unsigned short)); 39 arg.array = ptr; 40 //初始化信號量的值 41 for(i=0;i<nsems;i++) 42 ptr[i] = atoi(argv[i+2]); 43 //通過arg設置信號量集合 44 semctl(semid,0,SETALL,arg); 45 exit(0); 46 }
調用semctl獲取信號量的值,程序如下(semgetvalues.c):
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/sem.h> 5 #include <sys/ipc.h> 6 7 union semun 8 { 9 int val; 10 struct semid_ds *buf; 11 unsigned short *array; 12 }; 13 14 int main(int argc,char *argv[]) 15 { 16 int semid,nsems,i; 17 struct semid_ds seminfo; 18 unsigned short *ptr; 19 union semun arg; 20 if(argc != 2) 21 { 22 printf("usage: semgetvalues<pathname>"); 23 exit(0); 24 } 25 //打開已經存在的信號量 26 semid = semget(ftok(argv[1],0),0,0); 27 arg.buf = &seminfo; 28 //獲取信號量集的屬性,返回semid_ds結構 29 semctl(semid,0,IPC_STAT,arg); 30 nsems = arg.buf->sem_nsems; //信號量的數目 31 ptr = calloc(nsems,sizeof(unsigned short)); 32 arg.array = ptr; 33 //獲取信號量的值 34 semctl(semid,0,GETALL,arg); 35 for(i=0;i<nsems;i++) 36 printf("semval[%d] = %d\n",i,ptr[i]); 37 exit(0); 38 }
System V 信號量是具有內核的持續性,可以結合上面介紹的三個函數和程序進行簡單的測試。測試結果如下所示:
演示SEM_UNDO屬性,測試結果如下: