信号量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======


 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值