进程间通信——信号量

1、信号量介绍

    多个进程同时访问系统上的同一资源时,就需要考虑同步问题,以确保在任一时刻只有一个进程对资源独占式访问。通常,我们称访问共享资源的代码为临界区,进程同步,也就是确保同一时刻,只有一个进程进入临界区。

    信号量是实现进程同步的一种方式,信号量只能取自然数并且只支持两种操作,即等待和信号,在Linux中称为P、V操作。比如有SV信号量,则P、V操作的含义如下:

  • P(SV),如果SV的值大于0,就将其减1;如果SV的值等于0,则挂起该进程的执行
  • V(SV),如果有其他进程因为等待信号量SV而挂起,则唤醒;如果没有,则将SV加1

    信号量可以取任意自然数,但是最常用的信号量为二进制信号量,它只能取0和1,这里只讨论二进制信号量。关于信号量的系统调用主要有3个:semget、semop、semctl。此三个函数都是被设计用来操作信号量集,而不是单个信号量。

2、semget系统调用

#include<sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags)

    函数功能为用来创建一个信号量集或获取一个已经存在的信号量集,参数信息如下:

  • key参数是一个键值,用来标识一个全局的唯一的信号量集。通过信号量通信的进程使用相同的键值来创建\获取该信号量。当key为特殊键值IPC_PRIVATE(值为0)时,无论该信号量是否已经存在,都将会创建一个新的信号量集。信号量键值一般只有semget函数使用,其他的有关信号量函数,使用semget返回的信号量集标识符来访问该信号量。
  • num_sems参数指定要创建/获取的信号量集中的信号量数目。如果是创建信号量,该值必须被设置,如果是获取,则可以置为0。
  • sem_flags参数指定一组标志。低9位指示了信号量的权限。它可以和IPC_CREAT标志做按位或操作,用来创建信号量,此时即使信号量已经存在,semget也不会产生错误。还可以联合使用IPC_CREAT和IPC_EXCL标志来确保创建一组新的、唯一的信号量集,这种情况下,如果信号量集已经存在,则semget返回错误并置errno为EEXIST。
  • semget成功时返回信号量集标识符(正整数值),失败时返回-1,并设置errno。

3、semop系统调用

#include<sys/sem.h>
int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops)

    semop函数用来改变信号量的值,即执行P、V操作,参数信息如下:

  • sem_id参数是由semget调用返回的信号量集标识符,指定要改变的信号量集。
  • sem_ops参数是一个指向sembuf结构数组的指针。sembuf结构体定义如下:
struct sembuf
{
    unsigned short int sem_num;
    short int sem_op;
    short int sem_flg;
}

    sem_num成员指定信号量集中的信号量编号,从0开始。sem_op成员指定操作类型,其值可为正整数、0和负整数,信号量集操作类型较为复杂(可参见APUE),这里就只以二进制信号量为例来说明,sem_op为-1时即为P操作,为1时即为V操作。sem_flg成员可选值为IPC_NOWAIT和SEM_UNDO,IPC_NOWAIT表示系统调用无论是否操作成功,都将立即返回,类似于非阻塞;而SEM_UNDO则表示使内核跟踪信号量,如果进程没有释放信号量而异常退出,则由内核来释放该信号量值。

  • num_sem_ops指定sem_ops数组中的元素个数,即要操作的个数。
  • semop成功时返回0,失败时返回-1并设置errno。失败时,sem_ops数组中指定的所有操作都不被执行。

4、semctl系统调用

#include<sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...)

    semctl函数用于对信号量进行控制,参数信息如下:

  • sem_id参数是由semget调用返回的信号量集标识符。
  • sem_num参数指定被操作的信号量的编号,从0开始计数。
  • command参数指定要执行的命令,有的命令需要调用者传递第四个参数,第四个参数的类型由用户自定义,推荐格式如下:
union semun
{
    int val;
    struct semid_ds* buf;
    unsigned short* array;
    struct seminfo* _buf;
}

    command常用的操作有两种,其一,SETVAL,用来把信号量初始化为一个已知值,这个值由union semun中的val成员指定,其作用是在信号量初次使用前进行设置;其二,IPC_RMID,用于删除一个不需要再使用的信号量集,并唤醒所有等待该信号量集的进程。

5、使用IPC_PRIVATE信号量进行进程间通信示例

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

#define SEM_FLAGS 0666

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

void pv(int sem_id, int option)   //pv操作,option为-1时为p操作,option为1时为v操作
{
	struct sembuf sem_buf;
	sem_buf.sem_num = 0;
	sem_buf.sem_op = option;
	sem_buf.sem_flg = SEM_UNDO;
	semop(sem_id, &sem_buf, 1);
}

int main(int argc, char* argv[])
{
	int sem_id = semget(IPC_PRIVATE, 1, SEM_FLAGS);  //创建信号量
	union semun sem_un;
	sem_un.val = 1;
	semctl(sem_id, 0, SETVAL, sem_un);   //初始化信号量的值为1

	pid_t pid = fork();
	if(0 == id)
	{
		pv(sem_id, -1);
		printf("child process\n");
		sleep(5);
		pv(sem_id, 1);
		exit(0);
	}
	else if(id > 0)
	{
		pv(sem_id, -1);
		printf("parent process\n");
		sleep(5);
		pv(sem_id, 1);
	}
	else
	{
		printf("fork error\n");
		return 1;
	}

	wait(NULL);
	semctl(sem_id, 0, IPC_RMID, sem_un);   //删除信号量
	return 0;
}
posted @ 2018-05-18 15:05  落影无痕  阅读(549)  评论(0编辑  收藏  举报