1. 程式人生 > >linux程式設計——使用訊號量(第十四章)

linux程式設計——使用訊號量(第十四章)

14.1.4    使用訊號量

下面將用完整的程式設計介面為二進位制訊號量建立一個簡單得多的PV型別介面,然後用這個非常簡單的介面來演示訊號量是如何工作的。
程式sem1.c來試驗訊號量,該程式可以被多次呼叫。通過一個可選的引數來指定程式是負責建立訊號量還是負責刪除訊號量
用兩個不同字元的輸出來表示進入和離開臨界區域。如果程式啟動時帶有一個引數,它將在進入和退出臨界區域時列印字元X;而程式的其他執行例項將在進入和退出臨界區域時列印字元O。因為在任意時刻,只能有一個程序可以進入臨界區域,所以字元X和O應該是成對出現的。
訊號量
編寫程式sem1.c
/*************************************************************************
 > File Name:    sem1.c
 > Description:  sem1.c程式被用來試驗訊號量,該程式可以被多次呼叫,通過一個可選的引數來指定程式是負責建立訊號量還是負責刪除訊號量
 > Author:       Liubingbing
 > Created Time: 2015年07月17日 星期五 21時41分23秒
 > Other:        sem1.c程式用兩個不同字元的輸出來表示進入和離開臨界區域.
 				 如果程式啟動時帶有一個引數,它將在進入和退出臨界區域時列印字元X;而程式的其他示例將在進入和退出臨界區域時列印O
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/sem.h>
#include "semun.h"

static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);

static int sem_id;

int main(int argc, char *argv[])
{
	int i;
	int pause_time;
	char op_char = 'O';

	srand((unsigned int)getpid());

	/* semget函式的作用是建立一個新訊號量或取得一個已有訊號量的鍵
	 * 第一個引數key是整數值,不相關的程序可以通過它訪問同一個訊號量
	 * 第二個引數指定需要的訊號量數目,幾乎總是取1
	 * 第三個引數是一組標誌,與open函式的標誌非常類似,作用類似於檔案的訪問許可權 
	 * 如果成功則返回值訊號量識別符號(可以被其他訊號量函式使用) */
	sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);

	/* 如果程式是第一個被呼叫的(也就是說它在被呼叫時帶有一個引數,使得argc>1) */
	if (argc > 1) {
		/* set_semvalue函式初始化訊號量 */
		if (!set_semvalue()) {
			fprintf(stderr, "Failed to initialize semaphore\n");
			exit(EXIT_FAILURE);
		}
		/* 列印字元為'X' */
		op_char = 'X';
		sleep(2);
	}
	/* 下面這個迴圈,程式進入和離開臨界區域10次,在每次迴圈的開始,呼叫semaphore_p函式在程式進入臨界區域時設定訊號量以等待進入 */
	for (i = 0; i < 10; i++) {
		/*semaphore_p函式對訊號量做減1操作(等待),如果成功則返回1,如果失敗則返回0 */
		if (!semaphore_p())
			exit(EXIT_FAILURE);
		printf("%c", op_char);
		fflush(stdout);
		pause_time = rand() % 3;
		sleep(pause_time);
		printf("%c", op_char);
		fflush(stdout);

		/* 在臨界區域之後,呼叫semaphore_v函式來將訊號量設定為可用 */
		if (!semaphore_v())
			exit(EXIT_FAILURE);

		pause_time = rand() % 2;
		sleep(pause_time);
	}

	printf("\n%d - finished\n", getpid());

	if (argc > 1) {
		sleep(10);
		/* del_semvalue函式清理程式碼 */
		del_semvalue();
	}

	exit(EXIT_SUCCESS);
}

/* set_semvalue函式通過semctl呼叫的command引數設定SETVAL來初始化訊號量 
 * semctl函式用來直接控制訊號量資訊 */
static int set_semvalue(void) 
{
	union semun sem_union;
	
	sem_union.val = 1;
	/* semctl函式用來直接控制訊號量資訊
	 * 第一個引數sem_id是由semget返回的訊號量識別符號
	 * 第二個引數是訊號量編號,一般取值為0
	 * 第三個引數command是將要採取的動作,SETVAL標誌用來把訊號量初始化為一個已知的值,這個值通過union semun中的val成員設定
	 * 第四個引數是一個union semun結構
	 * 如果成功返回0,如果失敗返回-1 */
	if (semctl(sem_id, 0, SETVAL, sem_union) == -1) 
		return 0;
	return 1;
}

/* del_semvalue函式通過將semctl呼叫的command引數設定為IPC_RMID來刪除訊號量ID */
static void del_semvalue(void)
{
	union semun sem_union;
	/* semctl函式用來直接控制訊號量資訊
	 * 第一個引數sem_id是由semget返回的訊號量識別符號
	 * 第三個引數command是將要採取的動作,IPC_RMID表示用於刪除一個已知無需繼續使用的訊號量識別符號*/
	if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
		fprintf(stderr, "Failed to delete semaphore\n");
}

/* semaphore_p函式對訊號量做減1操作(等待) */
static int semaphore_p(void)
{
	struct sembuf sem_b;

	sem_b.sem_num = 0;			/* sem_num是訊號量編號,一般取值為0 */
	sem_b.sem_op = -1;			/* sem_op是訊號量在一次操作中需要改變的值,通常是-1和1,-1即P操作等待訊號量變為可用,1即V操作表示訊號量已可用*/
	sem_b.sem_flg = SEM_UNDO;	/* sem_flg一般設定為SEM_UNDO,它將使得作業系統跟蹤當前程序對這個訊號量的修改情況 */
	/* semop函式用於改變訊號量的值
	 * 第一個引數sem_id是由semget返回的訊號量識別符號
	 * 第二個引數是指向一個結構陣列的指標 */
	if (semop(sem_id, &sem_b, 1) == -1) {
		fprintf(stderr, "semaphore_p failed\n");
		return 0;
	}
	return 1;
}

/* semaphore_v函式對訊號量做加1操作(可用)*/
static int semaphore_v(void) 
{
	struct sembuf sem_b;

	sem_b.sem_num = 0;
	sem_b.sem_op = 1;			/* sem_op是訊號量在一次操作中需要改變的值,1即V操作,表示訊號量已可用 */
	sem_b.sem_flg = SEM_UNDO;
	/* semop函式用於改變訊號量的值 */
	if (semop(sem_id, &sem_b, 1) == -1) {
		fprintf(stderr, "semaphore_v failed\n");
		return 0;
	}
	return 1;
}
編寫程式semun.h
/*************************************************************************
 > File Name:    semun.h
 > Description:  semun.h程式定義了聯合semun
 > Author:       Liubingbing
 > Created Time: 2015年07月17日 星期五 21時48分04秒
 > Other:        semun.h
 ************************************************************************/

#ifndef _SEMUN_H
#define _SEMUN_H
#endif

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else

/* 聯合semun是semctl函式的第四個引數
 * semctl函式用來直接控制訊號量資訊 */
union semun {
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};
#endif
sem1.c在包含了必須的系統標頭檔案之後,包含標頭檔案semun.h,如果系統標頭檔案sys/sem.h沒有定義X/Open規範所需的聯合semun,這個標頭檔案包含了對它的定義。然後是函式原型的宣告和全域性變數的定義,呼叫函式semget來建立一個訊號量,該函式將返回一個訊號量識別符號。如果程式是第一個被呼叫的(也就是說它在被呼叫時帶有一個引數,使得argc>1),就呼叫set_semvalue初始化訊號量並將op_char設定為X。
這個簡單的程式值允許每個程式有一個二進位制訊號量,雖然可以通過傳遞訊號量變數的方法來擴充套件它可以支援更多的訊號量,但通常一個二進位制訊號量即已足夠。
可以通過多次啟動這個程式的方法來對它進行測試,第一次啟動時加上一個引數,表示應該由它來負責建立和刪除訊號量,其他的呼叫例項不使用引數。
下面是兩個程式呼叫例項時的一些輸出:

字元"O"和"X"分別代表程式的第一個和第二個呼叫例項。因為每個程式都在其進入和離開臨界區域時列印一個字元,所以每個字元都應該成對出現。如上圖所示,字元O和X都是成對出現的,這表明對臨界區域的處理是正確的。如果這個程式在系統上不能正常工作,可能需要在啟動程式之前執行命令stty -tostop,以確保不會產生tty輸出的後臺程式不會引發系統生成一個訊號。
程式分析
在程式的開始,用semget函式通過一個(隨機選取的)鍵來取得一個訊號量識別符號。IPC_CREAT標誌的作用是:如果訊號量不存在,就建立它。
如果程式帶有一個引數,它就負責訊號量的初始化工作,這是通過set_semvalue函式來完成的,該函式是針對更通用的semctl函式的簡化介面.程式還將根據是否帶有引數來決定需要列印哪個字元.sleep函式的作用是,有時間在這個程式例項執行太多迴圈之前呼叫其他的程式例項.用函式srand和rand來為程式引入一些偽隨機形式的時間分配.
接下來程式迴圈10次,在臨界區域和和非臨界區域會分別暫停一段隨機的時間.臨界區域由semaphore_p和semaphore_v函式前後把守,它們是更通用的semop函式的簡化介面.
刪除訊號量之前,帶有引數啟動的程式會進入等待狀態,以允許其他呼叫例項都執行完畢.如果不刪除訊號量,它將繼續在系統中存在,即使沒有程式在使用它也是如此.