雅乐网

计算机技术博客

编程 » Linux » Linux编程 信号量的使用

Linux编程 信号量的使用

多进程系统中,有时候多个进程同时访问一个资源,可能会发生与时间有关的错误。在一个时间点只允许一个程序访问的内存区域就叫做临界区,使用信号灯可以解决临界区问题。本文介绍Linux下提供的信号灯操作有关的系统调用。

Linux信号量函数包含在头文件 sys/sem.h里,有三个。

一、int semget(key_t key, int num_sems, int sem_flags)

semget的作用: 创建一个新信号量 或者 取得一个已有的信号量

第一个参数 key_t key

第一个参数是一个整数,你可以随便指定一个正整数。例如 程序A中 调用

idA = semget(2333, num_sems, sem_flags);

semget根据参数key,也就是2333(随便指定的),新创建一个信号量,返回这个信号量的标示(也是一个整数)。这个整数是系统分配的,用在其他信号量函数中用来说明对哪一个信号量操作。

如果在进程B中,再次使用

idB = semget(2333, num_sems, sem_flags);

这时候会 取得已有的信号量,也就是A中创建的那个,idB和idA的值是一样的。他们都标示了同一个信号量。

对于相同的key,semget函数返回相同的信号量标示,该标示可以用于指定信号量。

如果在B中不使用2333而是使用别的数字,那么B中创建一个新的信号量,和A中 的没有任何关系。

第二个参数 int num_sems

这也是一个整数,用来说明需要的信号量的数目。这是什么意思呢,上面idA就标示了一个信号量,这个信号量其实是一个数组,它有num_sems个信号灯。

一般num_sems取1,一个信号量只有一个信号灯,这样可以创建多个信号量来表示多个信号灯。

如果用一个信号量标示多个信号灯,用下标0 1 2 3区分看起来不是很直观。

第三个参数 int sem_flags

这个参数是一组标志,他和fopen函数的那些 “w” “b” “r”什么的有点类似。它的最低九位2进制数字代表了这个信号量的权限信息。

一般取值为 IPC_CREAT | 0666

宏定义的IPC_CREAT可以和其他进行或操作,它既可以用于新建信号量,同时又可以用于取一个已有的信号量。

使用IPC_CREAT | IPC_EXCL 来确保新建信号量,如果信号量已经有了会返回错误。

返回值

semget的返回值 失败的时候返回-1

成功的时候返回一个正整数,它是信号量的唯一标识,用来传给下面两个函数,对该信号量进行操作。

二、int semctl(int sem_id, int sem_num, int command, …)

semctl用于直接控制信号量的信息,例如初始化一个值或删除信号量。

第一个参数:int sem_id

用来表示对哪个信号量操作,它是由上面的semget函数返回的。

第二个参数:int sem_num

用来指定对哪个信号灯进行操作。如果创建信号量的时候设置了信号量的数目不为1,那就用这个来标示

0代表第一个信号量

1代表第二个信号量 以此类推

如果创建的信号量只有1个 ,那个这个参数传入0

第三个参数:int command

说明将要采取的动作。常用的有

SETCAL 用于给信号量赋初始值,通过第四个参数semun.val给定。

IPC_RMID 用于删除信号量

这两个符号都是宏定义的整数,更多命令可以查看它的手册。

第四个参数:union semun

如果需要第四个参数的话,就是这个。这个的声明至少有这几个成员

union semun{

int val;

struct semid_ds *buf;

unsigned short *array;

}

这个声明一般包含在sem.h里面,也有可能没有,没有的话需要自己声明。

返回值

根据command的不同,返回值也不同。对于 SETVAL和IPC_RMID 成功返回0 失败返回-1

三、int semop(int sem_id, struct sembuf * sem_ops, size_t num_sem_ops)

semop函数用于对信号量进行操作。

第一个参数 int sem_id

用来表示对哪个信号量操作,它是由上面的semget函数返回的。

第二个参数 struct sembuf *sem_ops

这是一个sembuf类型的结构体,这个结构体的声明,一般包含这几个成员

struct sembuf {

short sem_num;  //要处理的信号量的下标

short sem_op;     //要执行的操作

short sem_flg;     //操作标志

}

第一个sem_num是信号量下标,用来指定对哪个信号灯进行操作。

如果创建信号量的时候设置了信号量的数目不为1,那就用这个来标示

0代表第一个信号量

1代表第二个信号量 以此类推

如果创建的信号量只有1个 ,那个这个参数传入0

第二个 说明对信号量改变多少 1表示加1  -1表示减1

第三个 sem_flg通常为SEM_UNDO,这样操作系统会跟踪该信号量,在不使用时可以自动释放。

semop调用是一个原子操作,是一次性完成的,防止多个信号量竞争现象的出现。

第三个参数 size_t num_sem_ops

说明操作次数,一般为1 也就是操作1次。

信号量函数的进一步封装

直接用上面三个函数不免有些复杂,我们可以对我们经常用的操作封装成一个函数,例如创建信号灯,初始化信号灯以及信号灯的P、V操作还有删除信号灯。

创建信号灯

由于创建信号灯只需要一句semget函数即可,也可以不封装成自定义函数。在需要信号灯的时候我们只需要传入用来生成信号量的标示(任意正整数)以及信号量数量。写成函数其实更好,这里演示的是直接使用。

int sem_id;
sem_id = semget((key_t)1234, 2, 0666 | IPC_CREAT);
if (sem_id == -1)
    fprintf(stderr, "Failed to create semapore\n");

创建好的信号量用sem_id来标示。

第二行 1234是我们随意指定的一个整数,当然也可以用其他数字。如果在另一个程序代码中想还是用这个程序的信号量,那么就要用相同的整数。第二个参数2表示创建了2个信号量。第三个参数一般使用这个。。。

设置信号量的初始值

由于给初始值的时候需要一个临时联合体union semun类型,不妨把它写到一个更上层的函数。设置初始值需要知道信号量的标示(创建时候semget返回的值 也就是上面的sem_id),还需要对该信号量数组中的第几个操作(例如上面是2那么下标可能是0或1 分别表示第一个和第二个),还需要初始值。所以这个函数需要三个参数。

int set_semvalue(int sem_id, int index, int value)
{
		union semun sem_union;
		sem_union.val = value;

		if (semctl(sem_id, index, SETVAL, sem_union) == -1) 
			return 0;
                else
             		return 1;
}

 删除信号量

void del_semvalue(int sem_id)
{
	if (semctl(sem_id, 0, IPC_RMID) == -1)
		fprintf(stderr, "Failed to delete semapore\n");
}

 P操作

P操作是通过调用semop函数实现的。由于需要知道信号量标识以及第几个,需要两个参数。

int P(int sem_id, int index)
{
	struct sembuf sem_b;

	sem_b.sem_num = index;
	sem_b.sem_op = -1;
	sem_b.sem_flg = SEM_UNDO;

	if (semop(sem_id, &sem_b, 1) == -1)
	{
			fprintf(stderr, "semapore_p failed\n");
			return 0;
	}
	return 1;
}

 V操作

int V(int sem_id, int index)
{
		struct sembuf sem_b;

		sem_b.sem_num = index;
		sem_b.sem_op = 1;
		sem_b.sem_flg = SEM_UNDO;

		if (semop(sem_id, &sem_b, 1) == -1)
		{
				fprintf(stderr, "semapore_v failed\n");
				return 0;
		}
		return 1;
}

 使用信号量保证进程同步实例

(待续)

如果文章对你有帮助,欢迎点赞或打赏(金额不限)。你的打赏将全部用于支付网站服务器费用和提高网站文章质量,谢谢支持。

版权声明:

本文由 原创,商业转载请联系作者获得授权。
非商业转载请注明作者 雅乐网 ,并附带本文链接:
http://www.yalewoo.com/2014/12/30/linux_c_semaphore/

上一篇:

下一篇:

我要评论

*

验证码*: 7 + 0 =