【Linux】Linux的信号量集
所謂信號量集,就是由多個信號量組成的一個數(shù)組。作為一個整體,信號量集中的所有信號量使用同一個等待隊列。Linux的信號量集為進程請求多個資源創(chuàng)造了條件。Linux規(guī)定,當進程的一個操作需要多個共享資源時,如果只成功獲得了其中的部分資源,那么這個請求即告失敗,進程必須立即釋放所有已獲得資源,以防止形成死鎖。
?
信號量集的結(jié)構(gòu)
信號量結(jié)構(gòu)
描述信號量的內(nèi)核數(shù)據(jù)結(jié)構(gòu)如下:
struct sem {int semval; /* 信號量的當前值 */int sempid; /* 上一次操作本信號的進程PID */ };其中,域semval為一個整型變量,表示相應(yīng)共享資源的被占用情況;域sempid則記錄了上一次使用這個信號量的進程的標識號。
信號量集的結(jié)構(gòu)
如果把若干個信號量組成一個數(shù)組sem[],那么這個數(shù)組就是信號量集。使用信號量集可以同時把多個共享資源設(shè)置為互斥資源。
Linux用一個數(shù)組頭來管理這個信號量集,它包含信號量集的所有基本信息。
數(shù)組頭的結(jié)構(gòu)sem_array如下:
struct sem_array {struct kern_ipc_perm sem_perm; /* IPC許可結(jié)構(gòu) */time_t sem_otime; /* 上一次信號量的操作時間 */time_t sem_ctime; /* 信號量變化時間 */struct sem *sem_base; /* 指向信號量數(shù)組的指針 */struct list_head sem_pending; /* 等待隊列 */struct list_head list_id; /* undo結(jié)構(gòu) */unsigned long sem_nsems; /* 信號量集里面信號量的數(shù)目 */ };結(jié)構(gòu)的第一個域sem_perm為檢查用戶權(quán)限的許可結(jié)構(gòu)。數(shù)組頭結(jié)構(gòu)中的指針sem_base指向信號量數(shù)組,該數(shù)組中的每一個元素都是sem結(jié)構(gòu)的變量,即信號量。
一個信號量集的結(jié)構(gòu)如下圖所示:
從上圖可以看出,信號量集統(tǒng)一有一個進程等待隊列,而不是每個信號量都有一個,這正是信號量集的特點。
進程等待隊列結(jié)構(gòu)sem_queue如下:
struct sem_queue {struct list_head list; /* queue of pending operations */struct task_struct *sleeper; /* 指向等待進程控制塊的指針 */struct sem_undo *undo; /* undo請求操作結(jié)構(gòu)指針 */int pid; /* 請求操作的進程標識 */int status; /* 操作完成狀態(tài) */struct sembuf *sops; /* 掛起的操作集 */int nsops; /* 操作數(shù)目 */int alter; /* does the operation alter the array? */ };等待隊列是一個由進程控制塊所組成的隊列,每個進程控制塊代表著一個等待進程,sem_queue的域為sleeper指向了該等待隊列。
另外,為了使系統(tǒng)可以從等待進程控制塊中得到該進程所在的等待隊列,進程控制塊task_struct中有一個指向等待隊列的指針semsleeping。
內(nèi)核管理結(jié)構(gòu)
Linux系統(tǒng)所有的信號量集都注冊在一個數(shù)組中,該數(shù)組是內(nèi)核全局數(shù)據(jù)結(jié)構(gòu)struct ipc_id_ary的一個域。結(jié)構(gòu)struct ipc_id_ary的定義如下:
struct ipc_id_ary {int size;struct kern_ipc_perm *p[0]; //存放段描述結(jié)構(gòu)的數(shù)組 };結(jié)構(gòu)中的數(shù)組p[]就是信號量集的注冊數(shù)組。
數(shù)組p[]暫時只定義了0個元素,數(shù)組的長度在系統(tǒng)運行時會在相應(yīng)的操作里動態(tài)地增加或減少。
為了方便對上述數(shù)組進行管理,Linux又定義了一個數(shù)組頭struct ipc_ids。struct ipc_ids的定義如下:
struct ipc_ids {int in_use;unsigned short seq;unsigned short seq_max;struct rw_semaphore rw_mutex;struct idr ipcs_idr;struct ipc_id_ary *entries; //指向struct ipc_id_ary的指針 };很清楚,域entries就是指向信號量集數(shù)組的指針。
另外需要注意的是,由于為了充分利用內(nèi)存空間,進程消亡時需要及時釋放其所創(chuàng)建的信號量集,所以數(shù)組p[]的下標是動態(tài)的。這也就意味著以信號量在數(shù)組中的位置(下標)來作為標識不唯一,因此在上述結(jié)構(gòu)中有一個叫做序列號的域seq,系統(tǒng)每增加一個信號量集,系統(tǒng)就會將seq加1,然后把信號量集在數(shù)組p[]中的下標與之拼接起來形成唯一的標識,以供內(nèi)核來識別。
內(nèi)核對于信號量集的管理結(jié)構(gòu)如下圖所示:
?
信號量集的操作
信號量集的創(chuàng)建或打開
進程可以通過調(diào)用函數(shù)semget()創(chuàng)建或打開一個信號量集,這個函數(shù)是通過系統(tǒng)調(diào)用sys_semget()來實現(xiàn)的。系統(tǒng)調(diào)用sys_semget()的原型如下:
asmlinkage long sys_semget(key_t key, int nsems, int semflg);其中,參數(shù)key是用戶給定的鍵值;參數(shù)semflg是該函數(shù)的功能標志。
系統(tǒng)調(diào)用sys_semget()有兩個功能:如果參數(shù)semflg的IPC_CREATE的值給定為1,則這個系統(tǒng)調(diào)用會為用戶創(chuàng)建或打開一個信號量集,并返回信號量集標識符;如果為0,則會在系統(tǒng)已有的信號量集中尋找與鍵值相同的信號量集,找到后,打開該信號量集并返回信號量集的標識號。參數(shù)nsems用來指明在所創(chuàng)建的信號量集中信號量的個數(shù),即定義sem_base指向的數(shù)組的大小。
信號量集的操作
用于信號量操作的函數(shù)是semop()。為了用戶的方便,Linux提供了數(shù)據(jù)結(jié)構(gòu)sembuf,用戶在這個數(shù)據(jù)結(jié)構(gòu)中指明對信號量的操作。sembuf結(jié)構(gòu)定義如下:
struct sembuf {unsigned short sem_num; /* 信號量集在集中的序號 */short sem_op; /* 信號量操作 */short sem_flg; /* 操作標志 */ };其中,域sem_num指明待操作信號量在集中的位置;域sem_op就是對信號量的增量。通常,在訪問共享資源之前,域sem_op應(yīng)設(shè)為-1(對信號量進行減1的P操作);訪問之后,設(shè)為1(對信號量進行加1的V操作)。
前面講過,為了防止產(chǎn)生死鎖,信號量集的操作必須對集中的所有信號量同時操作,所以用戶還需要定義一個其長度與信號量數(shù)目相等的sembuf類型數(shù)組,以便把各個信號量的sembuf結(jié)構(gòu)數(shù)據(jù)存放到對應(yīng)的元素中。
函數(shù)semop()由系統(tǒng)調(diào)用sys_semop()實現(xiàn),其原型如下:
asmlinkage long sys_semop(int semid, struct sembuf __user *sops,unsigned nsops);其中,參數(shù)semid為信號量集的標識;參數(shù)sops就是指向上述sembuf數(shù)組的指針,數(shù)組每個元素都是對應(yīng)信號量集的操作結(jié)構(gòu)sembuf;參數(shù)nspos為這個數(shù)組的長度。
結(jié)構(gòu)undo
介紹信號量的基本原理時曾經(jīng)說過,P和V操作必須成對出現(xiàn)。也就是說,對于Linux信號量集,在臨界段前要用semop()來請求資源,而在臨界段后要用semop()來釋放資源,但在具體應(yīng)用中可能會因進程非正常中止而導(dǎo)致臨界段沒有機會來釋放資源。
如果有產(chǎn)生這種情況的可能,進程必須將釋放資源的任務(wù)轉(zhuǎn)交給內(nèi)核來完成。即在調(diào)用semop()請求資源時,把傳遞給函數(shù)的sembuf結(jié)構(gòu)的域sem_flg設(shè)置為SEM_UNDO。這樣,函數(shù)semop()在執(zhí)行時就會為信號量配置一個sem_undo的結(jié)構(gòu),并在該結(jié)構(gòu)中記錄釋放信號量的調(diào)整值;然后把信號量集中所有sem_undo組成一個隊列,并在等待進程隊列中用指針undo指向該隊列。
也就是說,通過設(shè)置SEM_UNDO,當進程非正常中止時內(nèi)核會產(chǎn)生響應(yīng)操作,以保證信號量處于正常狀態(tài)。
結(jié)構(gòu)sem_undo定義如下:
struct sem_undo {struct list_head list_proc; /* per-process list: all undos from one process. *//* rcu protected */struct rcu_head rcu; /* rcu struct for sem_undo() */struct sem_undo_list *ulp; /* sem_undo_list for the process */struct list_head list_id; /* per semaphore array list: all undos for one array */int semid; /* 信號量集標識符 */short * semadj; /* 存放信號量集調(diào)整值的數(shù)組指針 */ };于是,當系統(tǒng)執(zhí)行內(nèi)核函數(shù)do_ext()結(jié)束一個進程時,如果sembuf結(jié)構(gòu)中的sem_flg的值為SEM_UNDO,則該函數(shù)會掃描該進程的sem_undo隊列,并根據(jù)每個sem_undo結(jié)構(gòu)中的調(diào)整信息,依次調(diào)整各個信號量值,以釋放各個信號量。
信號量的控制
為實現(xiàn)對信號量的初始化等控制,Linux提供了函數(shù)semctl()。其對應(yīng)的內(nèi)核函數(shù)原型如下:
asmlinkage long sys_semctl(int semid, int semnum, int cmd, union semun arg);其中,semid為信號量集的表示;semnum為信號量的數(shù)目;cmd為操作命令;arg為信號量的初始值。
從參數(shù)定義中可知,arg的類型為union semun。該類型定義如下:
union semun {int val; /* 信號量初始值 */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; };內(nèi)核函數(shù)sys_semctl()將根據(jù)其命令參數(shù)cmd(第三個參數(shù))及參數(shù)arg來對信號量集實時控制。
?
進程控制塊中關(guān)于信號量集的域
進程使用信號量集的相關(guān)信息也被記錄在進程控制塊中。進程控制塊與信號量及相關(guān)的域如下:
struct task_struct {...struct sem_undo * semundo; //指向進程使用的信號量集undo隊列struct sem_queue * semsleeping; //指向進程所在等待隊列的指針... };其實就是兩個指針:一個指向進程使用的信號量undo隊列;另一個指向進程所在的等待隊列。
特別地,信號量集的undo隊列被組織在進程控制塊和信號量集兩個隊列中,如下圖所示:
?
總結(jié)
以上是生活随笔為你收集整理的【Linux】Linux的信号量集的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux C 基于epoll的多人聊天
- 下一篇: 关于iWebOffice中使用变量插入到