|
資源共用是UNIX多使用者系統的一個重要特徵,訊號量(SEMAPHORE)則是防止兩個或多個進程同時訪問共用資源的一種機制。在訊號量機制實現之前,通常採用加鎖檔案的方法,其演算法描述如下: ⑴加鎖演算法 int lock(lockfile) /*傳回值0代表成功,其它為失敗*/ char *lockfile; /*加鎖檔案名稱*/ { intfd,ret=0; extern int errno; if((fd=open(lockfile,O_WRONLY|O_CREAT|O_EXCL,0666))==-1 &&errno==EEXIST) ret=1; return(ret); } ⑵解鎖演算法 unlock(lockfile) char *lockfile; /*鎖檔案名稱*/ { unlink(lockfile); } 這種方法對訪問共用資源次數較少的進程是可行的,但對重載的使用則開銷太大了,況且一旦加鎖失敗則進程不知何時可以再試;當系統崩潰或重啟動時,加鎖檔案可能會被忘掉了。 Dijkstra發表的Dekker演算法給出了訊號量的一種實現,為整值對象定義了兩個了原語操作:P和V。其C描述如下: void P(sem) int *sem; { while (*sem<=0); (*sem)--; } void V(sem) int *sem; { (*sem)++; } 但上述演算法不能在使用者空間編程,因為①sem指向的訊號量變數不能在進程間共用,它們有自己的資料區段;②函數非原子執行,核心可在任何時候中斷一個進程;③若sem為0,進程並不釋放CPU。 所以訊號量必須由核心提供,它可在進程間共用資料,可執行原子操作(即一組操作要麼全部執行,要麼都不執行),可在一個進程阻塞時將CPU給另外一個進程。 UNIXSYSTEMV以一個長整數的索引值作為訊號量集合的唯一標識,訊號量通常由下列元素組成: ①訊號量的值, ②操作該訊號量的最後一個進程的進程標識, ③等待增加該訊號量的值的進程數, ④等待該訊號量的值為0的進程數。 與之有關的系統調用如下: #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key,count,flags) /*擷取訊號量集合的標識符*/ key_tkey; /*訊號量集合的鍵*/ intcount; /*訊號量集合中元素個數*/ intflags; /*任選參數*/ /*返回訊號量集合標識符,若出錯則返回-1*/ int semop(sid,ops,nops) /*訊號量操作*/ int sid; /*訊號量集合標識符*/ struct sembuf *ops; /*訊號量操作結構的指標*/ intnops; /*訊號量操作結構的個數*/ /*返回運算完成前該組訊號量中最後一個被運算的訊號量的 值,若出錯則返回-1*/ int semctl(sid,semnum,cmd,arg) /*控制訊號量操作*/ intsid; /*訊號量集合標識符*/ intsemnum; /*訊號量元素編號*/ intcmd; /*控制命令*/ union semun{ intval; struct semid_ds *buf; ushort*array;} arg; /*命令參數*/ 系統調用semget用來把訊號量集合的索引值譯成代表訊號量集合的標識符,該集合中有count個元素,其存取許可權定義與檔案相同,由flags定義。若flags的IPC_CREAT位被置位,則當該集合不存在時系統就建立之。因此各進程可都用置IPC_CREAT位的flags參數來擷取訊號量集合的標識符,不需要由某一進程事先建立。若flags為IPC_PRIDVATE則不管同索引值的訊號量集合是否存在系統都建立之,並返回下一個可用的標識符。 系統調用semctl在一組訊號量上做各種控制操作,諸如訊號量集合的初始化、刪除和狀態查詢等。常用的操作及相關的命令格式如下: ①取消訊號量集合 int semctl(sid,count,IPC_RMID,0) int sid; /*訊號量集合標識符*/ int count; /*訊號量集合中元素個數*/ ②設定訊號量集合的初值(初始化) 訊號量集合剛建立時,各訊號量的初值不確定,需要設定初值。初值的設定可用SETALL或SETVAL命令。若用SETALL命令,其格式為: int semctl(sid,count,SETALL,arg) int sid; /*訊號量集合標識符*/ int count; /*訊號量集合中元素個數*/ ushort *arg; /*命令參數*/ 該命令把數組arg中的前count個值依次賦給集合中各訊號量,一次可設定多個訊號量的初值。 若用SETVAL命令,其格式為: int semctl(sid,semnum,SETVAL,arg) int sid; /*訊號量集合標識符*/ int semnum; /*訊號量元素編號*/ int arg; /*命令參數*/ 該命令將arg的值賦給集合中第semnum個訊號量,一次僅能設定一個訊號量的初值。 ③查詢訊號量集合的當前值 查詢訊號量集合的當前值可用GETALL或GETVAL命令。若用GETALL命令,其格式為: int semctl(sid,count,GETALL,arg) int sid; /*訊號量集合標識符*/ int count; /*訊號量集合中元素個數*/ ushort *arg; /*命令參數*/ 該命令把訊號量集合中各訊號量的當前值返回到數組arg中。 若用GETVAL命令,其格式為: int semctl(sid,semnum,GETVAL,0) int sid; /*訊號量集合標識符*/ int semnum; /*訊號量元素編號*/ 該命令把集合中第semnum個訊號量的當前值作為調用的傳回值。 ④查詢某個訊號量的等待進程數 當一個進程要執行訊號量操作時若條件不具備則被阻塞,有關訊號量的等待進程數也相應變化。 通過GETNCNT命令可查詢等待訊號量增值的進程數,其格式如下: int semctl(sid,semnum,GETNCNT,0) int sid; /*訊號量集合標識符*/ int semnum; /*訊號量元素編號*/ 該命令把等待第semnum個訊號量增值的進程數作為調用的傳回值。 通過GETZCNT命令可查詢等待訊號量值為0的進程數,其格式如下: int semctl(sid,semnum,GETZCNT,0) int sid; /*訊號量集合標識符*/ int semnum; /*訊號量元素編號*/ 該命令把等待第semnum個訊號量值為0的進程數作為調用的傳回值。 至於其它的控制命令,因不常用而不再累述。 系統調用semop用來對訊號量集合中的一個或多個訊號量進行操作,操作命令由使用者提供的操作結構數組來定義,該結構如下: struct sembuf{ short sem_num; /*訊號量在集合中的下標*/ short sem_op; /*操作值*/ short sem_flg; /*操作標誌*/ }; 系統從使用者地址空間讀訊號量操作結構數組,並核實訊號量下標的合法性及進程是否具備讀或修改訊號量所必需的許可權。若許可權不夠則調用失敗;若進程必須睡眠,則它將已操作過的訊號量恢複為該系統調用開始時的值,然後它就睡眠,直到它等待的事件發生時再重新執行該系統調用。由於系統將運算元組儲存在一個全域數組中,因此若它必須重新執行該調用的話,它必須重新從使用者空間讀該數組。這樣,操作按原語方式執行--或一次做完或根本不做。 系統根據操作值來改變訊號量的值:①若操作值為正,系統就增加訊號量的值並喚醒所有等待訊號量增值的進程;②若操作值是0,系統就檢查訊號量的值:如果為0,就繼續數組中的其它操作;否則把等待訊號量的值為0的睡眠進程數加1,然後睡眠;③若操作值為負且其絕對值不超過訊號量的值,系統就把操作值(一個負數)加到訊號量值上,如果結果為0則系統就喚醒所有等待訊號量的值為0的睡眠進程;④若訊號量的值小於操作值的絕對值,系統就讓進程睡眠在"等待訊號量增值"這一事件上。 當進程在訊號量操作過程中睡眠時,它睡眠在可中斷級上,因此當它接收到非強制中斷訊號時就被喚醒了。使用者可在操作標誌中設定IPC_NOWAIT標誌以防止進程睡眠。 如果進程執行了一個訊號量操作,鎖住了某些資源,卻沒有恢複訊號量的值就退出了(如收到kill訊號),那麼就可能出現危險情況。為了避免這類問題,使用者可在操作標誌中設定SEM_UNDO標誌。當進程退出時,系統便撤除該進程做過的每個訊號量操作的影響。 值得指出的是,當你使用兩個或多個訊號量時,死結總是可能的,系統並不能檢查多個訊號量間的死結。 本文所用演算法及調用格式均已在SCOUNIX3.2、SCOOpenSever3.X及5.X上運行通過。 |