Linux进程间通信-信号量

        当多个进程表同时访问系统上的某个资源的时候,比如同时写一个数据库的某条记录,或者同时修改某个文件,就需要考虑进城的同步问题,以确保任一时刻只有一个进程可以拥有对资源的独占式访问。通常,程序对共享资源的访问的代码只是很短的一段,你就是这一段代码引发了进程之间的竞态条件。我们称这段代码为关键代码段,或者临界区。

        信号量是一种特殊的变量,它只能取自然数并只支持两种操作:等待(wait)和信号(signal),这两种操作更常见的称呼是P、V操作。假设有信号量SV,则对它的P、V操作含义如下:

l  P(SV),如果SV的值大于0,就将它减1:;如果SV的值为0,则挂起进程的执行。

l  V(SV),如果有其他进程因为等待SV二挂起,,则换星之;如果没有,则将SV加1。

        信号量API主要包含3个系统调用:semget、semop和semctl。它们都被设计为操作一组信号量,即信号量集,而不是单个信号量。

#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
int semctl(int sem_id, int sem_num, int command, ...);

semget函数调用创建一个新的信号量集或者获得一个存在的信号量。key参数是一个键值,用来标识一个全局唯一的信号量集,就像文件名全局唯一的标识一个文件一样。要通过信号量通信的进程需要使用相同的键值来创建或获取该信号量。

num_sems参数指定要创建或获取的信号量集中信号量的数目。如果是创建信号量,则该值必须指定;如果是获取已存在的信号量,则可以设置为0

sem_flags参数指定一组标志。它低端的9个比特是该信号量的权限,其格式和含义都与系统调用open的mode参数相同。我们可以和IPC_CREAT标志做按位“或”运算,此时即使信号量存在,semget也不会报错。我们还可以用IPC_CREAT和IPC_EXCL标志来确保创建一组新的、唯一的信号量集,此时若信号量存在,semget返回错误并设置errno为EEXIST。

semget调用成功时返回一个正整数,它是信号量集的标识符;失败时返回-1,并设置errno


semop函数改变信号量的值,即执行P和V操作

sem_id参数是由semget调用返回的信号量集标识符,用以指定被操作的目标信号量集。

sem_ops参数指向一个sembuf结构体的数组,定义如下:

struct sembuf
{
    unsigned short int sem_num;
    short int sem_op;
    short int sem_flg;
};

sem_num是信号量集中信号量的编号,像数组一样,从0开始。

sem_op指定操作类型,可选值为正整数、0、负整数。每种类型的操作行为又受到sem_flg的影响。

sem_flg的可选值是IPC_NOWAIT、SEM_UNDO。IPC_NOWAIT指无论信号量操作是否成功,semop调用都将立即返回,类似于非阻塞。SEM_UNDO指当进程退出时取消正在进行的semop操作。

num_sem_ops参数指定要执行的操作个数,即sem_ops数组中无素的个数。semop对数组中的每个成员按数组顺序依次执行,该过程是原子操作。

semop成功时返回0,失败时返回-1并设置errno


semctl函数允许调用者直接对信号量直接操作

sem_id参数是由semget调用返回的信号量集标识符,用以指定被操作的信号量集。

sem_num参数指定被操作的信号量在信号量集中的编号。

command参数指定要执行的命令。

有的命令需要第4个参数。其由用户自己定义,但sys/sem.h中给出了推荐格式:

union semun
{
    int val;                         //用于SETVAL命令
    struct semid_ds *buf;             //用于IPC_STAT和IPC_SET命令
    unsigned short *array;            //用于GETALL和SETALL命令
    struct seminfo *__buf;            //用于IPC_INFO命令
};
sem_ctl成功时的返回值取决于command参数,失败时返回-1并设置errno


特殊键值 IPC_PRIVATE(semget函数的key参数)

        semget调用者可以给其Key参数传递一个特殊的键值IPC_PRICATE(其值为0),这样无论该信号量是否已经存在,semget都将创建一个新的信号量。使用该键值创建的信号量并非像其他的名字声称的那样是进程私有的。其他进程,尤其是子进程,也有方法来访问这个信号量

使用IPC_PRIVATE的程序示例

#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define err_sys(msg) \
	do { perror(msg); exit(-1); } while(0)

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

void pv(int sem_id, int op)
{
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = op;
	sem_b.sem_flg = SEM_UNDO;
	semop(sem_id, &sem_b, 1);
}

int main(void)
{
	int sem_id = semget(IPC_PRIVATE, 1, 0666); /* 该信号量集大小为1 */

	union semun sem_un;
	sem_un.val = 1; /* 该该值为2时,表示可以同时两个进程执行P操作(即进行-1操作) */
	semctl(sem_id, 0, SETVAL, sem_un); /* 对信号量集中第一个信号进行操作 */

	pid_t pid = fork();
	if(pid < 0)
		err_sys("fork");
	else if(pid == 0)
	{
		pv(sem_id, -1);
		printf("child get the sem and would sleep 5s.\n");
		sleep(5);
		pv(sem_id, 1);
		exit(0);
	}
	else
	{
		pv(sem_id, -1);
		printf("parent get the sem and would sleep 5s.\n");
		sleep(5);
		pv(sem_id, 1);
	}

	waitpid(pid, NULL, 0);
	semctl(sem_id, 0, IPC_RMID, sem_un);

	return 0;
}

参考:

        1、《Linux高性能服务器编程》 第13章 多进程编程/信号量

        2、Linux多进程编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值