信號量2

 

說明:只供學習交流,轉載請註明出處

 

七,semctl函數

在使用信號量之前,需要對信號量集中的每個元素進行初始化操作。semctl函數提供了該項功能。該函數的具體信息如下表:

semctl函數

 

頭文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函數原型

int semctl(int semid, int semnum, int  cmd, …);

返回值

成功

失敗

是否設置errno

依賴於cmd參數

-1

 

說明:semctl函數提供了對信號量集的控制操作。參數semid爲要修改的信號量集的標識符。參數semnum用於指定修改信號量集中的semnum個信號量。cmd參數用於指定semctl函數的操作類型,可以取如下值:

IPC_STAT:將內核中的相關數據複製給指向semid_ds結構體中的arg.buf指針。semnum參數將被忽略。調用semctl函數的進程需要有訪問信號量集的權限。

IPC_SET:設置來自arg.buf的權限。

IPC_RMID:刪除semid參數中指定的信號量集。

IPC_INFOLinux特有的參數):獲得系統的信號量限制和arg.__buf中的信號量集信息,arg.__buf指向了seminfo結構體(sys/msg.h中定義,同時必須定義_GNU_SOURCE宏),seminfo結構體具體定義如下:

struct seminfo {

      int semmap; //信號量層映射裏的記錄數量

      int semmni;//最大信號量集

      int semmns;//所有信號量集的最大信號量集

      int semmnu;//系統最大信號量撤銷數;未使用

      int semmsl;//在信號量集中最多可容納的信號量數

      int semopm;//semop函數最大操作次數

      int semume;//每個進程最大信號量撤銷數;未使用

      int semusz;//結構體sem_undo的大小

      int semvmx; //最大信號量值

      int semaem; //可被記錄用於信號量的調整值。

};

 

SEM_INFOLinux特有的參數):將獲得與使用IPC_INFO一樣的信息。除此之外,semusz將獲得系統當前擁有的信號量集的數量,而semaem將獲得系統中所有信號量集中的信號量的個數。

SEM_STATLinux特有的參數):返回的參數與使用IPC_STAT相同。調用參數semid爲信號量集標識符,而是Linux內核內部用於維護系統信號量集的數組索引。

GETALL:在arg.array中返回信號量集的值。

GETNCNT:返回等待信號量增加的進程數。

GETPID:返回最後一個調用semop函數操作信號量集中信號量的進程號。

GETVAL:返回指定信號量集中信號量的值。

GETZCNT:返回等待信號量變爲0的進程數。

SETALL:用arg.array來設置信號量集的值。

SETVAL:將指定信號量集中的信號量設置爲arg.val

 

cmd參數爲上述的某些值時,需要使用arg參數來讀取或存儲返回的結果。參數arg的類型爲semun的聯合類型。使用該參數必須定義該類型,具體定義如下:

union semun {

      int val;             /*value for SETVAL */

      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;

};

 

semctl函數的返回值依賴於cmd參數的取值。除了以下的參數,semctl函數執行成功都返回0

GETNCNT:返回semncnt的取值。

GETPID:返回sempid的取值。

GETVAL:返回semval的取值。

GETZCNT:返回semzcnt的取值。

IPC_INFO:返回內核中信號量集數組的最高索引值。

SEM_INFO:等同於IPC_INFO

SEM_STAT:返回semid指定的信號量集標識符。

 

錯誤信息:

EACCES:進程函數調用失敗。

EFAULTarg.array指向非法地址空間。

EIDRM:信號量集被刪除。

EINVAL:參數cmdsemid的取值無效,或cmdSEM_STATsemid指向的信號量集索引未被使用。

EPERM:參數cmd取值爲IPC_SETIPC_RMID,但是調用進程的有效ID不是信號量的所有者或創建者。

ERANGEcmdSETALLSETVAL時,給出的semval超出範圍(0~SEMVMX)。

 

實例:

程序爲使用semctl獲得創建的信號量的實例。程序首先通過semget函數產生信號量,然後使用semctl函數獲得創建的信號量信息。

程序中使用了兩種參數來調用semctl函數:一種是IPC_STAT,該參數是POSIX標準中的參數,對應所有遵循POSIX標準的操作系統而言,使用該參數不會存在任何的問題;還有一種是Linux系統特有的參數,爲了保證程序的通用性,在代碼中通過宏開關來控制是否編譯使用Linux特有的參數。

由於在cmd參數中使用SEM_INFObuffer獲得的實際上是指向seminfo結構體的指針。因此,在使用前必須做強制類型轉換。而seminfo結構體的定義只有在定義了_GNU_SOURCE後纔可見,代碼中還必須先定義該宏。

具體代碼如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define LINUX_ENV

#ifdef LINUX_ENV
#define _GNU_SOURCE
#endif

union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short int *array;
	struct seminfo *__buf;
};

int main(void)
{
	key_t key;
	int proj_id;
	int semid;
	int nsems;
	union semun arg;
	struct semid_ds buffer;

	proj_id = 2;
	key = ftok("./program", 0666);
	if (key == -1)
	{
		perror("Cannot generate the IPC key");
		return (1);
	}

	nsems = 2;
	semid = semget(key, nsems, IPC_CREAT|0666);
	if (semid == -1)
	{
		perror("Cannot create semaphore set resource");
		return (1);
	}

	arg.buf = &buffer;
	if (semctl(semid, 0, IPC_STAT, arg) == -1)
	{
		perror("Cannot get semaphore set info");
		return (1);
	}

	printf("======semphore set info======\n");
	printf("effective user id : %d\n", buffer.sem_perm.uid);
	printf("effective group is : %d\n", buffer.sem_perm.gid);
	printf("semaphore set't creator user id : %d\n", buffer.sem_perm.cuid);
	printf("semaphore set't creator group id : %d\n", buffer.sem_perm.cgid);
	printf("access mode : %x\n", buffer.sem_perm.mode);

#ifdef LINUX_ENV

	if(semctl(semid, 0, SEM_INFO, arg) == -1)
	{
		perror("Cannot get semphore set info");
		return (1);
	}

	struct seminfo *sem_info;
	sem_info = (struct seminfo*)(&buffer);

	printf("Max. # of semaphore sets: %d \n", sem_info->semmni);
	printf("Max. # of semaphores in all semaphore sets : %d\n", sem_info->semmns);
	printf("Max. # of semaphores in a set : %d\n", sem_info->semmsl);
	printf("Max. # of operations for semop(): %d\n", sem_info->semopm);
	printf("size of struct sem_undo : %d\n", sem_info->semusz);
	printf("Max. value that can be recorded for semaphore adjustment (SEM_UNDO):%d\n",
							sem_info->semaem);

#endif

	return (0);
}

運行結果:
[root@localhost test]# ./semctl 
======semphore set info======
effective user id : 0
effective group is : 0
semaphore set't creator user id : 0
semaphore set't creator group id : 0
access mode : 1b6
Max. # of semaphore sets: 128 
Max. # of semaphores in all semaphore sets : 32000
Max. # of semaphores in a set : 250
Max. # of operations for semop(): 32
size of struct sem_undo : 3
Max. value that can be recorded for semaphore adjustment (SEM_UNDO):6


 

八,信號量集的操作

POSIX IPC提供了semop函數對信號量集中的信號量進行操作。semop函數的具體信息如下表所示:

semop函數

 

頭文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函數原型

int semop(int semid, struct sembuf *sops,  unsigned nsops);

返回值

成功

失敗

是否設置errno

0

-1

說明:semop函數用於在單個信號量集上執行sops參數指定的操作。在信號量集中的每個信號量都與下面的值相關。

unsigned short semval; //信號量的值

unsigned short semzcnt//等待信號量變爲0的進程數

unsigned short semncnt; //等待信號量增加的進程數

pid_t  sempid; //最後對信號量進行操作的進程號。

 

參數semid用於指定要進行操作的信號量集。參數sops指向了用於指定信號量操作的結構體。參數nsops給出了sops中要操作的信號量是哪個。

結構體sembuf定義如下:

struct sembuf {

      unsigned short  sem_num;    //信號量數值

      short        sem_op;        //要執行的操作

      short        sem_flg;  //操作指定的標誌位

};

 

sembuf中的成員sem_op表示信號量要執行的操作。如果是正數,表示讓信號量增加(即釋放資源);如果爲負數表示減小信號量的值(試圖獲取資源);爲0表示查看當前信號量是否爲0(所有資源都已經分配,處於使用狀態)。sem_flg可以取下面兩種值:

 

IPC_NOWAIT:如果信號量操作沒有完成(例如要減少信號量的值或測試其是否爲0),函數調用立即返回。

SEM_UNDO:如果沒有指定參數IPC_NOWAITSEM_UNDO將充許在阻塞操作調用失敗的情況下,撤銷操作。

 

 

下表爲sem_op取值與semop函數執行的操作之間的關係:

 

 

sem_op取值與semop函數執行的操作

 

sem_op取值

條件

Flag參數

semop函數執行的操作

sem_op<0

semval>|sem_op|

semval-|sem_op|

semval>=|sem_op|

SEM_UNDO

semval-|sem_op|,同時更新undo信號量的計數器

semval<|sem_op|

semncnt1,同時阻塞操作直到:semval>=|sem_op|,執行對應的操作。信號量集被刪除,返回出錯信息

sem_op>0

semval+sem_op

SEM_UNDO

semval+sem_op,同時更新undo信號量的計數器

 

錯誤信息:

E2BIG:參數nsops超過系統調用充許的值(SEMOPM)。

EACCES:調用進程沒有訪問特定信號量的權限。

EAGAIN:操作被阻塞,同時msg_flg中設置了IPC_NOWAIT或超過指定的等待時間。

EFAULT:參數sops指向非法的地址空間。

EFBIG:某個sops中的sem_num值過小,或超出信號量集中的信號量的數目。

EIDRM:信號量集被刪除。

EINTR:當系統調用被阻塞時,進程捕獲到了信號。

EINVAL:信號量集不存在,或semid小於0,或nsops爲非正數。

ENOMEMsem_flg某些操作指定了SEM_UNDO,系統用於保存這些undo結構的內存不足。

ERANGE:操作使得(sem_op+semval)的值大於系統最大信號量值(SEMVMX)。

 

實例:

程序使用信號量集實現進程互斥,以保護臨界區資源。程序首先創建信號集。這裏,在信號量集中只設置一個信號量。使用fork產生子進程,調用semget函數讓子進程獲得訪問信號量集的權限。在子進程中設置for循環,讓每個進程都進入兩次臨界區。完成者兩次進入後,子進程退出。父進程一直處於等待狀態,等待所有子進程結束。在所有子進程結束後,調用semctl函數刪除產生的信號量集。具體代碼如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short int *array;
	struct seminfo *__buf;
};

int main(int argc, char *argv[])
{
	int semid;
	pid_t pid;
	int proj_id;
	key_t key;
	int num;
	int i;
	int j;

	union semun arg;

	static struct sembuf acquire = {0, -1, SEM_UNDO};
	static struct sembuf release = {0, 1, SEM_UNDO};

	//獲取要產生的子進程數
	if ( argc != 2 )
	{
		printf("Usage: %s num\n", argv[0]);
		return (1);
	}

	num = atoi(argv[1]);

	proj_id = 3;
	key = ftok("./program", proj_id);
	if (key == -1)
	{
		perror("Cannot generate the IPC key");
		return (-1);
	}

	//生成進程互斥的信號量集,信號量集中設置一個信號量
	semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
	if (semid == -1)
	{
		perror("Cannot create semaphore set");
		return (-1);
	}

	//設置信號量集中的信號量的初始值爲1
	//static unsigned short start_var = 1;
	arg.array = (unsigned short*)1;
	if (semctl(semid, 0, SETVAL, arg) == -1)
	{
		perror("Cannot set semaphore set");
		return (-1);
	}

	for ( i = 0; i < num; i++ )
	{
		pid = fork();
		if ( pid < 0 )
		{
			perror("Cannot create new process");
			return (-1);
		}
		else if (pid == 0)
		{
			//讓子進程獲得訪問信號量的權限
			semid = semget(key, 1, 0);
			if (semid == -1)
			{
				perror("Cannot let the process get the access right");
				_exit(-1);
			}

			for (j = 0; j < 2; j++)
			{
				sleep(i);

				//acquire中的sem_op爲負數,表示獲取資源
				if (semop(semid, &acquire, 1) == -1)
				{
					perror("Cannot acquire the resource");
					_exit(-1);
				}

				//顯示進程進入臨界區的信息
				printf("======enter the critical section======\n");
				printf("-----pid : %ld ---\n", (long)getpid());
				sleep(1);
				printf("======leave the critical section======\n");

				//release中的sem_op爲正數,表示釋放資源
				if (semop(semid, &release, 1) == -1)
				{
					perror("Cannot release the resource");
					_exit(-1);
				}

				//_exit(0);
			}//end for{}

			_exit(0);
		}//end else if{}

	}

	//等待子進程結束
	for (i = 0; i < num; i++)
	{
		wait(NULL);
	}

	//刪除產生的信號量集
	if (semctl(semid, 0, IPC_RMID, 0) == -1)
	{
		perror("Cannot remove the semaphore set");
		return (-1);
	}

	return (0);

}

運行結果:
[root@localhost test]# ./semop 3
======enter the critical section======
-----pid : 12937 ---
======leave the critical section======
======enter the critical section======
-----pid : 12937 ---
======leave the critical section======
======enter the critical section======
-----pid : 12938 ---
======leave the critical section======
======enter the critical section======
-----pid : 12939 ---
======leave the critical section======
======enter the critical section======
-----pid : 12938 ---
======leave the critical section======
======enter the critical section======
-----pid : 12939 ---
======leave the critical section======


 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章