linux程序通訊之訊號燈(訊號量,semaphore)
訊號燈通訊,和一般意義的通訊不大一樣,通訊一般是用來收發資料,而訊號燈卻是用來控制多程序訪問共享資源的,利用這一功能,訊號量也就可以用做程序同步(實際上是執行緒間同步)。
訊號燈的當前值、有幾個程序在等待當前值變為0等等,這些資訊,可隨時用watch -n 0.1 ipcs -s -i semid命令來檢視,除錯階段很有用。
重要資料結構如下:(參考https://blog.csdn.net/xiaoyutao96/article/details/72720718)
<sys/sem.h> 訊號燈集的描述 struct semid_ds { struct ipc_perm sem_perm; ; //訊號燈集的訪問許可權 struct sem *sem_base; //描述每個訊號量的陣列的指標 ushort sem_nsems; //訊號量集中訊號量的數量 time_t sem_otime; //最後一次用semop函式操作該訊號燈集的時間 time_t sem_ctime; //本訊號燈集的semid_ds被更新時,該值會重新整理 … }; 訊號燈的描述: 其中成員sem_base的結構為: struct sem { ushort_t semval; //訊號量當前值 short sempid; //最後一次操作該訊號量的程序 ushort_t semncnt; //有幾個執行緒在等待semval變為大於當前值 (等待的程序/執行緒被堵塞) ushort_t semzcnt; //有幾個執行緒在等待semval變為0,(等待的程序/執行緒被堵塞) }; 訊號燈集操作許可權的描述: struct ipc_perm { // uid、gid、mode的低9bits,這三個域可通過semctl函式的IPC_SET命令來設定 key_t __key; /* 本訊號燈集是與哪一個key繫結的*/ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions */ unsigned short __seq; /* Sequence number */ };
資源共享和同步,靠的就是,根據訊號量的值,以及等待條件,實現程序/執行緒的堵塞等待(或者不堵塞而出錯返回)
相關API:
1、 int semget(key_t key, int nsems, int semflg);//建立或者開啟一個訊號燈集(注意:是訊號燈集,不是訊號燈!)
形參key、semflg不再贅述,和我的另一篇文章《linux程序通訊之訊息佇列》的佇列get函式的形參是一樣的,nsems用來設定所建立的訊號燈集中訊號燈的數目。
2、int semop(int semid, struct sembuf *sops, unsigned nsops);//訊號燈操作函式
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout)
這一結構描述了一個在特定訊號燈上的操作。在呼叫該函式之前,實際上我們已經靜態宣告或者動態malloc了一個struct sembuf的陣列,陣列的大小應該設定為多少?舉個例子,我們打算設定第0個訊號燈為等待值變為0,然後把第0訊號燈的值改為1,還打算把第1個訊號燈的值+1,這裡我們需要做3個動作,因此,陣列就宣告為3元素即可。sembuf結構的成員介紹:(參考https://blog.csdn.net/wanzyao/article/details/55271103)struct sembuf { unsigned short sem_num; /* 要操作的訊號燈在訊號燈集中的編號,同一個陣列中的多個元素可以操作同一個訊號量,見下面本函式的用例 */ short sem_op; /* 對訊號量的操作 */ short sem_flg; /* operation flags */ }
成員sem_op:程序/執行緒對某一個訊號量的操作,根據操作值的不同,程序/執行緒會發生不同的響應動作:
· 如果設定的sem_op>0,(程序對該訊號量集必須有write許可權)該值會被加到訊號量的當前值(semval)中,通常用於釋放所控資源的使用權(V操作);如果sem_flag指定了SEM_UNDO(還原)標誌,那麼相應訊號量的semadj值會減掉sem_op的值,下面會說明semadj的含義;這種>0的V操作永遠不會導致程序/執行緒堵塞。
· 如果sem_op=0,(程序對該訊號量集必須有read許可權),等待訊號量值變為0。當值不是0時,如果sem_flg設定了IPC_NOWAIT,函式立即返回並設定錯誤EAGAIN,如果沒有設定IPC_NOWAIT,則呼叫該函式的執行緒將睡眠,同時semzcnt的值自動+1,直到下列情況時才解除睡眠:①訊號量值變成了0(同時semzcnt的值自動-1);②該訊號量集被刪除,此時該函式立即返回並設定errno;③呼叫本函式的執行緒收到了訊號,此時該函式立即返回並設定errno,(同時semzcnt的值自動-1),除非建立訊號時,設定了SA_RESTART標誌;④達到了形參timeout的最大睡眠時間。
· 如果設定的sem_op<0,(程序對該訊號量集必須有write許可權),而訊號量的當前值(semval)≥ abs(sem_op),本函式會立即返回,同時執行semval -= abs(sem_op),這時如果sem_flg還設定了SEM_UNDO標誌,那麼abs(sem_op)還會被加到變數semadj上;如果訊號量的當前值(semval)< abs(sem_op),且設定了IPC_NOWAIT標誌,那麼本函式立即返回並設定errno,如果沒有設定IPC_NOWAIT,變數semncnt自動+1,然後本函式堵塞,直到下面情況時才解除睡眠(解除睡眠後,上文帶下劃線的句子也會被執行):①semval變的≥abs(sem_op)時;② 該訊號量集被刪除,此時該函式立即返回並設定errno;③呼叫本函式的執行緒收到了訊號,此時該函式立即返回並設定errno,(同時semncnt的值自動-1)除非建立訊號時,設定了SA_RESTART標誌;④形參timeout睡眠超時。<0時,用最簡單的一句話就是,<abs(sem_op)時,就要睡眠。
形參sops指向的陣列中的各項操作,會按照陣列順序,並以原子的方式執行,也即:這些操作要麼被全部執行完畢,要麼一個都不執行。(這句話似乎有問題,請移步另一篇博文:linux訊號量semaphore的幾種使用方法(semop函式的特性))
關於堵塞後被signal訊號打斷,可參見sigaction函式,簡單的說就是:預設情況下,系統呼叫(如semop)堵塞時,一旦收到了訊號,在執行完訊號處理函式後,返回系統呼叫,這時系統呼叫會立即出錯返回;然而,如果訊號處理函式收到的第二個實參的成員sa_flag包含了SA_RESTART標誌,那麼執行完訊號處理函式後,返回系統呼叫,系統呼叫不會出錯返回,而是重新執行一遍系統呼叫。
關於semadj,對某個程序,在指定SEM_UNDO後,對訊號量semval值的修改都會反應到semadj上,當該程序終止的時候,核心會根據semadj的值,重新恢復訊號量之前的值,這種機制可以防止程序/執行緒意外終止時,佔用的資源得不到釋放。
成員sem_flg:可取的值為IPC_NOWAIT、SEM_UNDO,分別介紹如下:
SEM_UNDO:當程序/執行緒結束時,程序/執行緒對該訊號燈的狀態會恢復為原樣,主要是為了避免這一問題:執行緒鎖住了某些資源,結果程式設計師的疏忽忘了釋放,或者本來是要釋放的,結果尚未釋放之前執行緒崩潰了,這兩種情況都會造成資源死鎖。
IPC_NOWAIT: 對訊號的操作不能滿足時,semop()不會阻塞,並立即返回,同時設定錯誤資訊。
形參:@timeout:當semtimedop()呼叫致使程序進入睡眠時,睡眠時間不能超過本引數指定的值。如果睡眠超時,semtimedop()將失敗返回,並設定錯誤值為EAGAIN。如果本引數的值為NULL,semtimedop()將永遠睡眠等待,這時其功能就相當於semop了。
本函式執行成功以後,sempid成員和sem_otime成員會被更新。
返回值:成功返回0;失敗返回-1並設定errno
用例(來自官方幫助手冊):
功能描述:假設我們已經建立好了一個訊號量集,裡面包含多個訊號量(編號依次為0、1、2···),現在要求我們利用訊號量0,來堵塞一個執行緒,直到訊號量0的當前值變為0時,才解除堵塞:
struct sembuf sops[5];//注意:元素的數目和訊號量集中訊號量的數目沒有任何關係,陣列的多個元素可以操作同一個訊號量
int semid;
/* Code to set semid omitted */
sops[0].sem_num = 0; /* 要操作的訊號量的編號為0 */
sops[0].sem_op = 0; /* Wait for value to equal 0 */
sops[0].sem_flg = 0; /* 既不IPC_NOWAIT,也不IPC_UNDO */
sops[1].sem_num = 0; /* 要操作的訊號量的編號為0 */
sops[1].sem_op = 1; /* >0 的作用是,把第sem_num個訊號量的當前值+sem_op */
sops[1].sem_flg = 0; /* 既不IPC_NOWAIT,也不IPC_UNDO */
if (semop(semid, sops, 2) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
程式碼講解:前文已經講過,形參傳入的sops所描述的操作,要麼一次全執行完,要麼一個都不執行:semop第三引數設定要執行sops設定的的前兩個操作,第一個sops操作是讓執行緒堵塞地等待第0個訊號量的值變為0,然後第2個sops操作把訊號量0的當前值+1,這兩個操作都執行完以後,執行緒發現第0個訊號量的值不是0,於是就堵塞了。3、int semctl(int semid, int semnum, int cmd, ...);訊號燈集的控制
描述:用於控制訊號燈集semid中的第semnum個訊號燈。根據cmd的不同,可以有3或4個形參,第四個形參必須為聯合體,型別為:
union semun {
int val; /* cmd=SETVAL時使用 */
struct semid_ds *buf; /* cmd=IPC_STAT、 IPC_SET時使用 */
unsigned short *array; /* cmd= GETALL、 SETALL時使用 */
struct seminfo *__buf; /* cmd=IPC_INFO (Linux-specific) 時使用*/
};
cmd可選的值有很多(執行相應命令的程序必須有相應的操作許可權、使用者組許可權等):
(1)IPC_STAT,讀訊號燈集的狀態(也即讀本訊號燈集的semid_ds資料結構),讀出的內容從第4形參傳出(聯合體中有struct semid_ds),該命令是針對整個訊號燈集的,顯然該命令會忽略形參semnum。
(2)IPC_SET,設定訊號燈集的狀態,還是用semid_ds資料結構,該命令並不能設定semid_ds的所有成員,只能設定sem_perm成員的gid、uid、mode三個成員,同時本函式會更新sem_ctime。顯然該命令也會忽略形參semnum。
(3)IPC_RMID,刪除訊號量集(一定會刪除成功)。一旦刪除,所有被semop堵塞地執行緒/程序,都會被喚醒,喚醒後的後果是semop函數出錯返回,前文介紹過相關內容了了。顯然該命令會忽略形參semnum。
(4)IPC_INFO,讀取整個作業系統中訊號量的限制數目、引數等資訊,通過第四形參傳出,資料結構為聯合體中的struct seminfo,定義如下。開啟巨集_GNU_SOURCE之後才能用該命令
struct seminfo
{
int semmap; /* Number of entries in semaphore
map; unused within kernel */
int semmni; /* Maximum number of semaphore sets */
int semmns; /* Maximum number of semaphores in all
semaphore sets */
int semmnu; /* System-wide maximum number of undo
structures; unused within kernel */
int semmsl; /* Maximum number of semaphores in a
set */
int semopm; /* Maximum number of operations for
semop(2) */
int semume; /* Maximum number of undo entries per
process; unused within kernel */
int semusz; /* Size of struct sem_undo */
int semvmx; /* Maximum semaphore value */
int semaem; /* Max. value that can be recorded for
semaphore adjustment (SEM_UNDO) */
};
(5)GETNCNT 讀取有幾個執行緒在等待第semnum個訊號量值變大;
(6)GETZCNT 讀取有幾個執行緒在等待第semnum個訊號量值變為0;
(7)GETPID 讀取最後一個通過semop函式操作第semnum個訊號量的那個程序id;
(8)GETVAL 讀取第semnum個訊號量的當前值semval;
(9)SETVAL 設定第semnum個訊號量的當前值semval;
(10)GETALL 讀取訊號燈集中所有訊號燈的當前值semval(通過第四形參聯合體中的array成員指向的陣列傳出),顯然該命令也會忽略形參semnum;
(11)SETALL 設定訊號燈集中所有訊號燈的當前值semval(通過第四形參聯合體中的array成員指向的陣列),顯然該命令也會忽略形參semnum;
(12)SEM_INFO和SEM_STAT功能類似於IPC_INFO、IPC_STAT,區別不是很大,自行查閱man手冊。
返回值:若失敗,則返回-1,並設定errno,若成功,則返回值(一定是≥0的)根據cmd的不同而不同:
例如,讀數量返回數量,讀pid返回pid,
應用舉例,需求描述:子程序和父程序都要列印幾個字串,要求兩者的列印不能混在一塊,如果父程序先列印了,那麼子程序必須等待父程序列印完之後,子程序才能開始列印,如果子程序先列印了,那麼情況同理。
//編譯: gcc -o sem_exe semaphore.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
//二值訊號量(互斥量)的使用例子
void V(int semid) //佔用資源(拿走綠燈)
{
struct sembuf sops[5];
sops[0].sem_num = 0; /* 要操作的訊號量的編號為0 */
sops[0].sem_op = 0; /* Wait for value to equal 0 */
sops[0].sem_flg = 0; /* 既不IPC_NOWAIT,也不IPC_UNDO */
sops[1].sem_num = 0; /* 要操作的訊號量的編號為0 */
sops[1].sem_op = 1; /* >0 的作用是,把第sem_num個訊號量的當前值+sem_op */
sops[1].sem_flg = SEM_UNDO; /* IPC_NOWAIT,SEM_UNDO. */
if (semop(semid, sops, 2) == -1)
{
printf("V->semop failed, infor:%s\r\n", strerror(errno) );
exit(1);
}
}
void P(int semid) //釋放資源(歸還綠燈)
{
struct sembuf sops[5];
sops[0].sem_num = 0; /* 要操作的訊號量的編號為0 */
sops[0].sem_op = -1; /* -1 */
sops[0].sem_flg = SEM_UNDO; /* IPC_NOWAIT,SEM_UNDO. */
if (semop(semid, sops, 1) == -1)
{
printf("P->semop failed, infor:%s\r\n", strerror(errno) );
exit(1);
}
}
int main(int argc, char *argv[])
{
printf("exe file path: %s\r\n", argv[0] );
key_t key = ftok(argv[0], 'f');
if(-1 == key)
{
printf("ftok failed, infor:%s\r\n", strerror(errno) );
return -1;
}
else
{
printf("ftok ok, key = %d\r\n", key );
}
int semid = semget(key, 1, IPC_CREAT | 0666 );//集合中只要一個燈
if(-1 == semid)
{
printf("semget failed, infor:%s\r\n", strerror(errno) );
return -1;
}
else
{
printf("semget ok, semid = %d\r\n", semid );
}
//V(semid);//父程序佔用資源
pid_t pid = fork();
if(pid == 0 )//子程序
{
printf("child process start\r\n");
V(semid);
int i;
for(i = 0; i <= 2; i++)
{
printf("child print: %d\r\n", i);
sleep(1);
}
printf("child released semaphore!\r\n");
P(semid);
printf("child process return\r\n");
_exit(0);
}
else if(pid > 0)//父程序
{
//sleep(1);
V(semid);//父程序佔用資源
int i, stat;
for(i = 0; i <= 2; i++)
{
printf("father print: %d\r\n", i);
sleep(1);
}
printf("father released semaphore!\r\n");
printf("father is waiting child to end\r\n");
P(semid);
if(waitpid(pid, &stat, 0) != pid)
{
printf("child process failed!\r\n");
}
else
{
printf("child process returned value = %d\r\n", stat);
}
printf("father process return\r\n");
}
else
{
printf("fork failed, infor:%s\r\n", strerror(errno) );
return -1;
}
return 0;
}
相關推薦
linux程序通訊之訊號燈(訊號量,semaphore)
訊號燈通訊,和一般意義的通訊不大一樣,通訊一般是用來收發資料,而訊號燈卻是用來控制多程序訪問共享資源的,利用這一功能,訊號量也就可以用做程序同步(實際上是執行緒間同步)。訊號燈的當前值、有幾個程序在等待當前值變為0等等,這些資訊,可隨時用watch -n 0.1 ipcs -
Android 進階12:程序通訊之 Socket (順便回顧 TCP UDP)
不要害怕困難,這是你進步的機會! 讀完本文你將瞭解: 前面幾篇文章我們介紹了 AIDL 、Binder、Messenger 以及 ContentProvider 實現程序通訊的方式,這篇文章將介紹“使用 Socket 進行跨程序通訊”。 在介紹
程序通訊之 Socket (順便回顧 TCP UDP)
不要害怕困難,這是你進步的機會! 讀完本文你將瞭解: 前面幾篇文章我們介紹了 AIDL 、Binder、Messenger 以及 ContentProvider 實現程序通訊的方式,這篇文章將介紹“使用 Socket 進行跨程序通訊”。 在介紹 Socket 之前我們先來回顧一下網路基礎知識
Linux 程序通訊之 ——訊號和訊號量總結
現在最常用的程序間通訊的方式有:訊號,訊號量,訊息佇列,共享記憶體。 所謂程序通訊,就是不同程序之間進行一些"接觸",這種接觸有簡單,也有複雜。機制不同,複雜度也不一樣。通訊是一個廣義上的意義,不僅僅指傳遞一些massege。他們的使用方法是基本相同的,所以只要
Linux 程序通訊之:記憶體共享(Shared Memory)
一、簡介 共享記憶體允許兩個程序訪問同一塊記憶體區域,它們使用同一個 key 值標記。 二、特點 優點: 通訊方便,兩個程序也是直接訪問同一塊記憶體區域,減少了資料複製的操作,速度上也有明顯優勢。 缺點: 沒有提供同步機制,往往需要我們使用其它(例如訊號)等手段實
Linux 程序通訊之:管道 (Pipe)
一、簡介 管道(pipe) 是一種最基本的 IPC(Inter Process Communication) 機制,優點是簡單。 二、特點: 管道只適用於 存在血緣關係 的兩個程序之間通訊,因為只有存在血緣關係的兩個程序之間才能共享檔案描述符 管道分為兩端,一端
Linux 程序通訊之:記憶體對映(Memory Map)
一、簡介 正如其名(Memory Map),mmap 可以將某個裝置或者檔案對映到應用程序的記憶體空間中。通過直接的記憶體操作即可完成對裝置或檔案的讀寫。. 通過對映同一塊實體記憶體,來實現共享記憶體,完成程序間的通訊。由於減少了資料複製的次數,一定程度上提高了程序間通訊的效率。
Linux 程序通訊之:命名管道 (FIFO)
一、簡介 由於管道(Pipe)只能應用於存在血緣關係的程序間通訊,為了突破這個限制,使用命名管道(FIFO)來實現 不相干程序間 的通訊。 FIFO 是 Linux 基礎檔案型別中的一種,它並不佔用磁碟上實際的資料塊,而僅僅是標識核心中的一條通道。各程序可以開啟
Linux程序通訊之訊號
文章轉自 http://blog.csdn.net/maimang1001/article/details/16906451 鑑於後面把程序的形象給徹底毀掉了,我提前宣告一下,程序是有尊嚴的有節操的,當然大部分人可能也看不到毀形象那一段。為什麼介紹linux要從訊號
學習Linux程序設計之路(1)UNIX程序設計哲學
文件 系統 領域 mil 自己 編程 代碼 復雜 容易 UNIX程序編程有自己特定的風格,我們在學習UNIX程序設計的時候,應該盡量遵從這種設計風格,它能夠最大限度地幫助我們避免一些問題。簡單化 許多有用並且好用的UNIX系統軟件都非常簡單,而且很小並易於理解。
Linux程序管理之狀態(二)
二、程序的生命週期 程序是一個動態的實體,所以他是有生命的。從建立到消亡,是一個程序的整個生命週期。在這個週期中,程序可能會經歷各種不同的狀態。一般來說,所有程序都要經歷以下的3個狀態: 就緒態。指程序已經獲得所有所需的其他資源,正在申請處理處理器資源,準備開始執行。這種情況下,稱程序處於就緒態。
linux程序管理之概念(一)
一、程序和執行緒的概念 1.程序和執行緒的定義 程序並不只是一段可以執行的程式碼,也包含了執行程式碼所需要的資源。 在作業系統來看,程序是資源管理的最小單元,而我們又知道,執行緒是程式執行的最小單元。 話說回來,Linux系統至少有一個程序,一個程式可以對應多個程序,一個程序只能對應一個程
Linux程序通訊--無名管道(pipe)和有名管道(FIFO)通訊
管道通訊 管道是單向的、先進先出的,它把一個程序的輸出和另一個程序的輸入連線在一起。一個程序(寫程序)在管道的尾部寫入資料,另一個程序(讀程序)從管道的頭部讀出資料 管道建立 管道包括無名管道和有名管道兩種,前者用於父程序和子程序的通訊,後者可用於運行於同一系統中的任意兩個程序間
Python實戰之執行緒(函式多執行緒,類多執行緒,守護執行緒,GIL,執行緒lock,遞迴Rlock,Semaphore訊號量,event)
首先須知 什麼是IO? 從硬碟讀塊資料,從網路讀塊資料屬於IO操作(IO操作不佔用cpu) 計算佔用cpu,如:1+1 Python多執行緒其實就是一個執行緒,由於利用CUP的上下文切換看起來就像是併發..上下文切換消耗資源 Python多執行緒 不適合CPU密集操作型的任務,適
程序通訊之管道(PIPE)
在前面程序通訊概念和程序通訊方式,我們瞭解了程序通訊的簡單概念以及4種程序通訊的方式,今天我們將要通過具體例項來學習,理解程序通訊方式中的管道(PIPE)。 本文所有程式碼都在Ubuntu16.04測試。 我們在前面已經瞭解了常用的程序間通訊方式,它們大
Linux程序通訊之管道和FIFO
Linux程序間的通訊可以簡稱為IPC(Interprocess Communication),前面說過的 Linux的同步工具也是屬於IPC的一部分,這裡我想說的是通常意義的程序間的實際資料通。 1管道 管道是最早的UNIX IPC,所有的UNIX系統都支援這個IPC通訊機制。我們最常見到使用它的位置就是
Linux 程序通訊之 命名管道
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main() { int fb =
Linux程序通訊之訊息佇列的雙向通訊
上一篇部落格我寫了程序間通訊基於管道的通訊,但是管道的通訊無疑有很大的缺點,最顯著的就是隻能單向通訊,例如:server向client發訊息,client無法回覆;第二個就是隻能在有血緣關係的程序間進行通訊,雖然命名管道解決了第二點,但是第一點還是一個很大的
第18章 ARM Linux裝置樹之四(常用的OF API)
18.4 常用的OF API除了前文介紹的of_machine_is_compatible()、of_device_is_compatible()等常用函式以外,在Linux的BSP和驅動程式碼中,經常會使用到一些Linux中其他裝置樹的API,這些API通常被冠以of_字首
運維筆記40 Linux系統監控之Cacti(Cacti搭建,自動抓取cacti統計圖片指令碼)
概述:監控系統在一個系統中十分重要,它會將很多重要的資訊,諸如記憶體資訊,cpu資訊,硬碟資訊集合在一起顯示出來,當系統出現問題的時候我們能及時定位並修復錯誤。今天介紹的監控系統是一款輕量級的監控系統Cacti,並附有python編寫的cacti統計圖片採集指令