① linux pthread 信號量 佔用資源嗎
glibc提供的pthread互斥信號量可以用在進程內部,也可以用在進程間,可以在初始化時通過pthread_mutexattr_setpshared介面設置該信號量屬性,表示是進程內還是進程間。進程內的使用較為簡單,本文的總結主要是針對進程間的,進程內的也可以參考,其代碼實現原理是類似的。
一、實現原理
pthread mutex的實現是非常輕量級的,採用原子操作+futex系統調用。
在沒有競爭的情況下,即鎖空閑時,任務獲取信號量只需要通過原子操作鎖的狀態值,把值置為佔有,再記錄其他一些俄信息(owner,計數,如果使能回收功能則串入任務的信號量回收鏈表等),然後就返回了。
如果在獲取鎖時發現被佔用了,如果調用者需要睡眠等待,這時候會觸發futex系統調用,由內核繼續處理,內核會讓調用任務睡眠,並在適當時候喚醒(超時或者鎖狀態為可用)。
佔用鎖的任務釋放鎖時,如果沒有任務等待這把鎖,只需要把鎖狀態置為空閑即可。如果發現有其他任務在等待此鎖,則觸發futex系統調用,由內核喚醒等待任務。
由此可見,在沒有競爭的情況下,mutex只需要在用戶態操作鎖狀態值,無須陷入內核,是非常高效的。
獲取到鎖的任務沒有陷入內核,那麼當鎖支持優先順序翻轉時,高優先順序任務等待這把鎖,正常處理必須提升佔用鎖的任務優先順序。內核又是怎麼知道是哪個任務佔用了鎖呢?實現上,復用了鎖的狀態值,該值在空閑態時為0,非空閑態則保存了鎖的持有者ID,即PID,內核態通過PID就知道是那個任務了。
二、內核對鎖的管理
內核維護了一個hash鏈表,每把鎖都被插入到hash鏈表中去,hash值的計算如下(參考get_futex_key):1,如果是進程內的鎖,則通
過鎖的虛擬地址+任務mm指針值+鎖在頁內偏移;2,如果是進程間的鎖,則會獲取鎖虛擬地址對應物理地址的page描述符,由page描述符構造
hash值。
這樣計算的原因是進程間的鎖在各個進程內虛擬地址可能是不同的,但都映射到同一個物理地址,對應同一個page描述符。所以,內
核使用它來定位是否同一個鎖。
這里對進程間互斥鎖計算hash值的方法,給進程間共享鎖的使用設置了一個隱患條件。下面描述這個問題。
三、進程間互斥信號量的使用限制:必須在系統管理的內存上定義mutex結構,而不能在用戶reserved的共享內存上定義mutex結構。
鎖要實現進程間互斥,必須各個進程都能看到這個鎖,因此,鎖結構必須放在共享內存上。
獲取系統的共享內存通過System V的API介面創建:shmget, shmat,shmdt。但是shmget的參數需要一個id值,各進程映射同一塊共享內存需要同樣的ID值。如果各個進程需要共享的共享內存比較多,如幾千上萬個,ID值如果管理?shmget的man幫助和一些示例代碼給出的是通過ftok函數把一個文件轉為ID值(實際就是把文件對應的INODE轉為ID值),但實際應用中,如果需要的共享內存個數較多,難道創建成千上萬個文件來使用?而且怎麼保證文件在進程的生命周期內不會被刪除或者重建?
當時開發的系統還存在另外一種共享內存,就是我們通過remap_pfn_range實現的,自己管理了這塊內存的申請釋放。申請介面參數為字元串,相同的字元串表示同一塊內存。因此,傾向於使用自己管理的共享內存存放mutex結構。但在使用中,發現這種方法達不到互斥的效果。為什麼?
原因是自己管理的共享內存在內核是通過remap_pfn_range實現的,內核會把這塊內存置為reserved,表示非內核管理,獲取鎖的HASH值時,查找不到page結構,返回失敗了。最後的解決方法還是通過shmget申請共享內存,但不是通過ftok獲取ID,而是通過字元串轉為ID值並處理沖突。
四、進程間互斥信號量回收問題。
假設進程P1獲取了進程間信號量,異常退出了,還沒有釋放信號量,這時候其他進程想來獲取信號量,能獲取的到嗎?
或者進程P1獲取了信號量後,其他進程獲取不到進入了睡眠後,P1異常退出了,誰來負責喚醒睡眠的進程?
好在系統設計上已經考慮了這一點。
只要在信號量初始化時調用pthread_mutexattr_setrobust_np設置支持信號量回收機制,然後,在獲取信號量時,如果原來佔有信號量的進程退出了,系統將會返回EOWNERDEAD,判斷是這個返回值後,調用pthread_mutex_consistent_np完成信號量owner的切換工作即可。
其原理如下:
任務創建時,會注冊一個robust list(用戶態鏈表)到內核的任務控制塊TCB中期,獲取了信號量時,會把信號量掛入鏈表。進程復位時,內核會遍歷此鏈表(內核必須非常小心,因為此時的鏈表信息可能不可靠了,可不能影響到內核),置上ownerdead的標志到鎖狀態,並喚醒等待在此信號量鏈表上的進程。
五、pthread介面使用說明
pthread_mutex_init: 根據指定的屬性初始化一個mutex,狀態為空閑。
pthread_mutex_destroy: 刪除一個mutex
pthread_mutex_lock/trylock/timedlock/unlock: 獲取鎖、釋放鎖。沒有競爭關系的情況下在用戶態只需要置下鎖的狀態值即返回了,無須陷入內核。但是timedlock的入參為超時時間,一般需要調用系統API獲取,會導致陷入內核,性能較差,實現上,可先trylock,失敗了再timedlock。
pthread_mutexattr_init:配置初始化
pthread_mutexattr_destroy:刪除配置初始化介面申請的資源
pthread_mutexattr_setpshared:設置mutex是否進程間共享
pthread_mutexattr_settype:設置類型,如遞歸調用,錯誤檢測等。
pthread_mutexattr_setprotocol:設置是否支持優先順序翻轉
pthread_mutexattr_setprioceiling:設置獲取信號量的任務運行在最高優先順序。
每個set介面都有對應的get介面。
六、pthread結構變數說明
struct __pthread_mutex_s
{
int __lock; ----31bit:這個鎖是否有等待者;30bit:這個鎖的owner是否已經掛掉了。其他bit位:0鎖狀態空閑,非0為持有鎖的任務PID;
unsigned int __count; ----獲取鎖的次數,支持嵌套調用,每次獲取到鎖值加1,釋放減1。
int __owner; ----鎖的owner
unsigned int __nusers; ----使用鎖的任務個數,通常為1(被佔用)或0(空閑)
int __kind;----鎖的屬性,如遞歸調用,優先順序翻轉等。
int __spins; ----SMP下,嘗試獲取鎖的次數,盡量不進入內核。
__pthread_list_t __list; ----把鎖插入回收鏈表,如果支持回收功能,每次獲取鎖時要插入任務控制塊的回收鏈表。
}__data;
② Linux信號量
信號量是包含一個非負整數型的變數,並且帶有兩個原子操作wait和signal。Wait還可以被稱為down、P或lock,signal還可以被稱為up、V、unlock或post。在UNIX的API中(POSIX標准)用的是wait和post。
對於wait操作,如果信號量的非負整形變數S大於0,wait就將其減1,如果S等於0,wait就將調用線程阻塞;對於post操作,如果有線程在信號量上阻塞(此時S等於0),post就會解除對某個等待線程的阻塞,使其從wait中返回,如果沒有線程阻塞在信號量上,post就將S加1.
由此可見,S可以被理解為一種資源的數量,信號量即是通過控制這種資源的分配來實現互斥和同步的。如果把S設為1,那麼信號量即可使多線程並發運行。另外,信號量不僅允許使用者申請和釋放資源,而且還允許使用者創造資源,這就賦予了信號量實現同步的功能。可見信號量的功能要比互斥量豐富許多。
POSIX信號量是一個sem_t類型的變數,但POSIX有兩種信號量的實現機制: 無名信號量 和 命名信號量 。無名信號量只可以在共享內存的情況下,比如實現進程中各個線程之間的互斥和同步,因此無名信號量也被稱作基於內存的信號量;命名信號量通常用於不共享內存的情況下,比如進程間通信。
同時,在創建信號量時,根據信號量取值的不同,POSIX信號量還可以分為:
下面是POSIX信號量函數介面:
信號量的函數都以sem_開頭,線程中使用的基本信號函數有4個,他們都聲明在頭文件semaphore.h中,該頭文件定義了用於信號量操作的sem_t類型:
【sem_init函數】:
該函數用於創建信號量,原型如下:
該函數初始化由sem指向的信號對象,設置它的共享選項,並給它一個初始的整數值。pshared控制信號量的類型,如果其值為0,就表示信號量是當前進程的局部信號量,否則信號量就可以在多個進程間共享,value為sem的初始值。
該函數調用成功返回0,失敗返回-1。
【sem_destroy函數】:
該函數用於對用完的信號量進行清理,其原型如下:
成功返回0,失敗返回-1。
【sem_wait函數】:
該函數用於以原子操作的方式將信號量的值減1。原子操作就是,如果兩個線程企圖同時給一個信號量加1或減1,它們之間不會互相干擾。其原型如下:
sem指向的對象是sem_init調用初始化的信號量。調用成功返回0,失敗返回-1。
sem_trywait()則是sem_wait()的非阻塞版本,當條件不滿足時(信號量為0時),該函數直接返回EAGAIN錯誤而不會阻塞等待。
sem_timedwait()功能與sem_wait()類似,只是在指定的abs_timeout時間內等待,超過時間則直接返回ETIMEDOUT錯誤。
【sem_post函數】:
該函數用於以原子操作的方式將信號量的值加1,其原型如下:
與sem_wait一樣,sem指向的對象是由sem_init調用初始化的信號量。調用成功時返回0,失敗返回-1。
【sem_getvalue函數】:
該函數返回當前信號量的值,通過restrict輸出參數返回。如果當前信號量已經上鎖(即同步對象不可用),那麼返回值為0,或為負數,其絕對值就是等待該信號量解鎖的線程數。
【實例1】:
【實例2】:
之所以稱為命名信號量,是因為它有一個名字、一個用戶ID、一個組ID和許可權。這些是提供給不共享內存的那些進程使用命名信號量的介面。命名信號量的名字是一個遵守路徑名構造規則的字元串。
【sem_open函數】:
該函數用於創建或打開一個命名信號量,其原型如下:
參數name是一個標識信號量的字元串。參數oflag用來確定是創建信號量還是連接已有的信號量。
oflag的參數可以為0,O_CREAT或O_EXCL:如果為0,表示打開一個已存在的信號量;如果為O_CREAT,表示如果信號量不存在就創建一個信號量,如果存在則打開被返回,此時mode和value都需要指定;如果為O_CREAT|O_EXCL,表示如果信號量存在則返回錯誤。
mode參數用於創建信號量時指定信號量的許可權位,和open函數一樣,包括:S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH、S_IWOTH。
value表示創建信號量時,信號量的初始值。
【sem_close函數】:
該函數用於關閉命名信號量:
單個程序可以用sem_close函數關閉命名信號量,但是這樣做並不能將信號量從系統中刪除,因為命名信號量在單個程序執行之外是具有持久性的。當進程調用_exit、exit、exec或從main返回時,進程打開的命名信號量同樣會被關閉。
【sem_unlink函數】:
sem_unlink函數用於在所有進程關閉了命名信號量之後,將信號量從系統中刪除:
【信號量操作函數】:
與無名信號量一樣,操作信號量的函數如下:
命名信號量是隨內核持續的。當命名信號量創建後,即使當前沒有進程打開某個信號量,它的值依然保持,直到內核重新自舉或調用sem_unlink()刪除該信號量。
無名信號量的持續性要根據信號量在內存中的位置確定:
很多時候信號量、互斥量和條件變數都可以在某種應用中使用,那這三者的差異有哪些呢?下面列出了這三者之間的差異:
③ linux 信號量是什麼怎麼用
Linux信號量(semaphore)是一種互斥機制。即對某個互斥資源的訪問會收到信號量的保護,在訪問之前需要獲得信號量。
在操作完共享資源後,需釋放信號量,以便另外的進程來獲得資源。獲得和釋放應該成對出現。
獲得信號量集,需要注意的是,獲得的是一個集合,而不是一個單一的信號量。
#include
#include
#include
1: int semget(key_t key,int nsems,int semflg);
key:系統根據這個值來獲取信號量集。
nsems:此信號集包括幾個信號量。
semflg:創建此信號量的屬性。 (IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR)
成功則返回該信號量集的ID。
註:
既指定IPC_CREAT又指定IPC_EXCL時,如果系統中該信號量集已經存在,則馬上返回。
如果需要獲得存在的信號量,則將此參數置0.
2: int semctl(int semid,int senum,int cmd....)
semid:信號量ID。
senum:對信號量集中的第幾個信號量進行控制。(從0開始)
cmd:需要進行的操作。(SETVAL是其中的一個)。
根據cmd的不同可能存在第四個參數,cmd=SETVAL時,表示同時信號量可以被獲得幾次,如第四個參數
num=1表示只能被獲得一次,既被信號量保護的資源只能同時被一個程序使用。
該系統調用,是在對信號量初始化時用的。
-3: 「3」前面加了"-"表示當需要使用互斥資源時應該做這步。
int semop(int semid,struct sembuf *sem,int num_elements);
struct sembuf {
unsigned short sem_num; //該信號量集中的第幾個信號量。
int sem_op;//需要獲得還是釋放信號量
int sem_flg;//相關動作
};
num_elements:需要對該信號量集中的多少個信號量進行處理。
獲得信號量時,將sembuf結構提初始化為:
sem_num = 0; //該信號量集中的首個信號量
sem_op = -1; //獲得信號量
sem_flag = IPC_NOWAIT; //如果不能獲得信號量,馬上返回。
semop(semid,_sem,1);
同理釋放信號量時,將sem_op設為1.
以上是對信號量的簡單處理
④ Linux下信號量的加減操作問題
void down(struct semaphore *sem); //不可中斷
int down_interruptible(struct semaphore *sem);//可中斷
int down_killable(struct semaphore *sem);//睡眠的進程可以因為受到致命信號而被喚醒,中斷獲取信號量的操作。
int down_trylock(struct semaphore *sem);//試圖獲取信號量,若無法獲得則直接返回1而不睡眠。返回0則 表示獲取到了信號量
int down_timeout(struct semaphore *sem,long jiffies);//表示睡眠時間是有限制的,如果在jiffies指明的時間到期時仍然無法獲得信號量,則將返回錯誤碼。