內核為每個信號量集合設置了一個semid_ds結構
struct semid_ds { struct ipc_permsem_perm ; structsem* sem_base ; //信號數組指針 ushort sem_nsem ; //此集中信號個數 time_t sem_otime ; //最后一次semop時間 time_t sem_ctime ; //最后一次創建時間} ;每個信號量由一個無名結構表示,它至少包含下列成員: (這個是什么意思??)struct { ushort_t semval ; //信號量的值 short sempid ; //最后一個調用semop的進程ID ushort semncnt ; //等待該信號量值大于當前值的進程數(一有進程釋放資源 就被喚醒) ushort semzcnt ; //等待該信號量值等于0的進程數} ;三 信號量的使用1、創建信號量semget函數創建一個信號量集或訪問一個已存在的信號量集。#include <sys/sem.h>int semget (key_t key, int nsem, int oflag) ;返回值是一個稱為信號量標識符的整數,semop和semctl函數將使用它。參數nsem指定集合中的信號量數。(若用于訪問一個已存在的集合,那就可以把該參數指定為0)參數oflag可以是SEM_R(read)和SEM_A(alter)常值的組合。(打開時用到),也可以是IPC_CREAT或IPC_EXCL ;2、打開信號量使用semget打開一個信號量集后,對其中一個或多個信號量的操作就使用semop(op--Operate)函數來執行。#include <sys/sem.h>int semop (int semid, struct sembuf * opsptr, size_t nops) ;參數opsptr是一個指針,它指向一個信號量操作數組,信號量操作由sembuf結構表示:
struct sembuf{ short sem_num; // 除非使用一組信號量,否則它為0 short sem_op; // 信號量在一次操作中需要改變的數據,通常是兩個數, // 一個是-1,即P(等待)操作,一個是+1,即V(發送信號)操作 short sem_flg; // 通常為SEM_UNDO,使操作系統跟蹤信號,并在進程沒有釋放該信號量而終止時, // 操作系統釋放信號量 };◆參數nops規定opsptr數組中元素個數。sem_op值:(1)若sem_op為正,這對應于進程釋放占用的資源數。sem_op值加到信號量的值上。(V操作)(2)若sem_op為負,這表示要獲取該信號量控制的資源數。信號量值減去sem_op的絕對值。(P操作)(3)若sem_op為0,這表示調用進程希望等待到該信號量值變成0◆如果信號量值小于sem_op的絕對值(資源不能滿足要求),則:(1)若指定了IPC_NOWAIT,則semop()出錯返回EAGAIN。(2)若未指定IPC_NOWAIT,則信號量的semncnt值加1(因為調用進程將進 入休眠狀態),然后調用進程被掛起直至:①此信號量變成大于或等于sem_op的絕對值;②從系統中刪除了此信號量,返回EIDRM;③進程捕捉到一個信 號,并從信號處理程序返回,返回EINTR。(與消息隊列的阻塞處理方式 很相似)3、信號量是操作semctl函數對一個信號量執行各種控制操作。#include <sys/sem.h>int semctl (int semid, int semnum, int cmd, /*可選參數*/ ) ;第四個參數是可選的,取決于第三個參數cmd。參數semnum指定信號集中的哪個信號(操作對象)參數cmd指定以下10種命令中的一種,在semid指定的信號量集合上執行此命令。IPC_STAT 讀取一個信號量集的數據結構semid_ds,并將其存儲在semun中的buf參數中。IPC_SET 設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。IPC_RMID 將信號量集從內存中刪除。GETALL 用于讀取信號量集中的所有信號量的值。GETNCNT 返回正在等待資源的進程數目。GETPID 返回最后一個執行semop操作的進程的PID。GETVAL 返回信號量集中的一個單個的信號量的值。GETZCNT 返回這在等待完全空閑的資源的進程數目。SETALL 設置信號量集中的所有的信號量的值。SETVAL 設置信號量集中的一個單獨的信號量的值。四 信號量值的初始化semget并不初始化各個信號量的值,這個初始化必須通過以SETVAL命令(設置集合中的一個值)或SETALL命令(設置集合中的所有值) 調用semctl來完成。
SystemV信號量的設計中,創建一個信號量集并將它初始化需兩次函數調用是一個致命的缺陷。一個不完備的解決方案是:在調用semget時指定IPC_CREAT | IPC_EXCL標志,這樣只有一個進程(首先調用semget的那個進程)創建所需信號量,該進程隨后初始化該信號量。
五 例子#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <sys/sem.h> union semun{ int val; struct semid_ds *buf; unsigned short *arry;}; static int sem_id = 0; static int set_semvalue();static void del_semvalue();static int semaphore_p();static int semaphore_v(); int main(int argc, char *argv[]){ char message = 'X'; int i = 0; /* 創建信號量 */ sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT); if(argc > 1) { /* 程序第一次被調用,初始化信號量 */ if(!set_semvalue()) { fPRintf(stderr, "Failed to initialize semaphore/n"); exit(EXIT_FAILURE); } /* 設置要輸出到屏幕中的信息,即其參數的第一個字符 */ message = argv[1][0]; sleep(2); } for(i = 0; i < 10; ++i) { /* 進入臨界區 */ if(!semaphore_p()) { exit(EXIT_FAILURE); } /* 向屏幕中輸出數據 */ printf("%c", message); /* 清理緩沖區,然后休眠隨機時間 */ fflush(stdout); sleep(rand() % 3); /* 離開臨界區前再一次向屏幕輸出數據 */ printf("%c", message); fflush(stdout); /* 離開臨界區,休眠隨機時間后繼續循環 */ if(!semaphore_v()) { exit(EXIT_FAILURE); } sleep(rand() % 2); } sleep(10); printf("/n%d - finished/n", getpid()); if(argc > 1) { /* 如果程序是第一次被調用,則在退出前刪除信號量 */ sleep(3); del_semvalue(); } exit(EXIT_SUCCESS);} static int set_semvalue(){ /* 用于初始化信號量,在使用信號量前必須這樣做 */ union semun sem_union; sem_union.val = 1; if(semctl(sem_id, 0, SETVAL, sem_union) == -1) { return 0; } return 1;} static void del_semvalue(){ /* 刪除信號量 */ union semun sem_union; if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1) { fprintf(stderr, "Failed to delete semaphore/n"); }}static int semaphore_p(){ /* 對信號量做減1操作,即等待P(sv)*/ struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1;//P() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed/n"); return 0; } return 1;} static int semaphore_v(){ /* 這是一個釋放操作,它使信號量變為可用,即發送信號V(sv)*/ struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1;//V() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_v failed/n"); return 0; } return 1;}六 信號量集合的例子#include<stdio.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>#include<errno.h>#include<string.h>#include<stdlib.h>#include<assert.h>#include<time.h>#include<unistd.h>#include<sys/wait.h>#define MAX_SEMAPHORE 10#define FILE_NAME "test2.c" union semun{ int val ; struct semid_ds *buf ; unsigned short *array ; struct seminfo *_buf ;}arg;struct semid_ds sembuf; int main(){ key_t key ; int semid ,ret,i; unsigned short buf[MAX_SEMAPHORE] ; struct sembuf sb[MAX_SEMAPHORE] ; pid_t pid ; pid = fork() ; if(pid < 0) { /* Create process Error! */ fprintf(stderr,"Create Process Error!:%s/n",strerror(errno)); exit(1) ; } if(pid > 0) { /* in parent process !*/ key = ftok(FILE_NAME,'a') ; if(key == -1) { /* in parent process*/ fprintf(stderr,"Error in ftok:%s!/n",strerror(errno)); exit(1) ; } semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666); //創建信號量集合 if(semid == -1) { fprintf(stderr,"Error in semget:%s/n",strerror(errno)); exit(1) ; } printf("Semaphore have been initialed successfully in parent process,ID is :%d/n",semid); sleep(2) ; printf("parent wake up..../n"); /* 父進程在子進程得到semaphore的時候請求semaphore,此時父進程將阻塞直至子進程釋放掉semaphore*/ /* 此時父進程的阻塞是因為semaphore 1 不能申請,因而導致的進程阻塞*/ for(i=0;i<MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = -1 ; /*表示申請semaphore*/ sb[i].sem_flg = 0 ; } printf("parent is asking for resource.../n"); ret = semop(semid , sb ,10); //p() if(ret == 0) { printf("parent got the resource!/n"); } /* 父進程等待子進程退出 */ waitpid(pid,NULL,0); printf("parent exiting .. /n"); exit(0) ; } else { /* in child process! */ key = ftok(FILE_NAME,'a') ; if(key == -1) { /* in child process*/ fprintf(stderr,"Error in ftok:%s!/n",strerror(errno)); exit(1) ; } semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666); if(semid == -1) { fprintf(stderr,"Error in semget:%s/n",strerror(errno)); exit(1) ; } printf("Semaphore have been initialed successfully in child process,ID is:%d/n",semid); for(i=0;i<MAX_SEMAPHORE;++i) { /* Initial semaphore */ buf[i] = i + 1; } arg.array = buf; ret = semctl(semid , 0, SETALL,arg); if(ret == -1) { fprintf(stderr,"Error in semctl in child:%s!/n",strerror(errno)); exit(1) ; } printf("In child , Semaphore Initailed!/n"); /* 子進程在初始化了semaphore之后,就申請獲得semaphore*/ for(i=0;i<MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = -1 ; sb[i].sem_flg = 0 ; } ret = semop(semid , sb , 10);//信號量0被阻塞 if( ret == -1 ) { fprintf(stderr,"子進程申請semaphore失敗:%s/n",strerror(errno)); exit(1) ; } printf("child got semaphore,and start to sleep 3 seconds!/n"); sleep(3) ; printf("child wake up ./n"); for(i=0;i < MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = +1 ; sb[i].sem_flg = 0 ; } printf("child start to release the resource.../n"); ret = semop(semid, sb ,10) ; if(ret == -1) { fprintf(stderr,"子進程釋放semaphore失敗:%s/n",strerror(errno)); exit(1) ; } ret = semctl(semid ,0 ,IPC_RMID); if(ret == -1) { fprintf(stderr,"semaphore刪除失敗:%s!/n",strerror(errno)); exit(1) ; } printf("child exiting successfully!/n"); exit(0) ; } return 0;}【信號量的意圖在于進程間同步,互斥鎖和條件變量的意圖則在于線程間同步。但是信號量也可用于線程間,互斥鎖和條件變量也可用于進程間。我們應該使用適合具體應用的那組原語。】
|
新聞熱點
疑難解答