1. 程式人生 > >linux程序通訊之訊號燈(訊號量,semaphore)

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)

;//訊號燈操作函式

形參semid指定了訊號燈集,形參sops指向的陣列的每一個元素,每一個元素都指定了針對燈集中的一個燈的操作。形參:@semid 訊號燈集的id@sops 指向一個struct sembuf型別的陣列,陣列的大小由形參nsops傳入,struct sembuf的成員為:
struct sembuf 
{
    unsigned short sem_num;  /* 要操作的訊號燈在訊號燈集中的編號,同一個陣列中的多個元素可以操作同一個訊號量,見下面本函式的用例 */
    short          sem_op;   /* 對訊號量的操作 */
    short          sem_flg;  /* operation flags */
}
這一結構描述了一個在特定訊號燈上的操作。在呼叫該函式之前,實際上我們已經靜態宣告或者動態malloc了一個struct sembuf的陣列,陣列的大小應該設定為多少?舉個例子,我們打算設定第0個訊號燈為等待值變為0,然後把第0訊號燈的值改為1,還打算把第1個訊號燈的值+1,這裡我們需要做3個動作,因此,陣列就宣告為3元素即可。sembuf結構的成員介紹:(參考https://blog.csdn.net/wanzyao/article/details/55271103)

成員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程序設計1UNIX程序設計哲學

文件 系統 領域 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系統監控CactiCacti搭建自動抓取cacti統計圖片指令碼

概述:監控系統在一個系統中十分重要,它會將很多重要的資訊,諸如記憶體資訊,cpu資訊,硬碟資訊集合在一起顯示出來,當系統出現問題的時候我們能及時定位並修復錯誤。今天介紹的監控系統是一款輕量級的監控系統Cacti,並附有python編寫的cacti統計圖片採集指令