Linux进程通信——信号量

概念

信号量(semaphore) 与已经介绍过的 PC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

特点

1.信号量用于进程间同步,若要在进程间传递数据需要结合共享内存
2.信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作

PV操作 一种实现进程互斥与同步的有效方法。PV操作与信号量的处理相关,P表示通过的意思,V表示释放的意思
原子操作 指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个线程)

3.每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数
4.支持信号量组

使用原理

最简单的信号量是只能取 0和 1的变量,这也是信号量最常见的一种形式,叫做二值信号量 Binary Semaphore) 。而可以取多个正整数的信号量被称为通用信号量

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

semget函数

创建或获取一个信号量组:若成功返回信号量标识符ID,失败返回-1。

函数原型

int semget(key_t key, int nsems, int semflg);

参数解读

key 所创建或打开信号量集的键值
nsems 信号量的个数,通常为1;如果是引用一个现有的集合,则将nsems指定为 0,该参数只在创建信号量集时有效
semflg 调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过 | 表示

semctl函数

控制信号量的相关信息。

函数原型

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

参数解读

semid semget函数返回的信号量标识符ID
semnum 操作信号在信号集中的编号,这里是以数组为单位的,所以第一个单位是数组中的第一个,即为0
cmd 对信号量进行相关操作

在semctl函数中的cmd有多种,这里就说两个常用的:

SETVAL 用于初始化信号量为一个已知的值。所需要的值作为联合体semun的val成员来传递,在信号量第一次使用之前需要设置信号量
IPC_RMID 删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源

如果有第四个参数,它通常是联合体union semun,定义格式如下:

union semun
{
	int val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
};

semop函数

对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1。

函数原型

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

参数解读

semid semget函数返回的信号量标识符ID
*sops 通常是一个结构体数组,但信号量理论上一次只能执行一个,所以在定义该结构体时,不需要以数组格式定义
nsops 信号操作结构的数量,恒大于或等于1

*sops结构体定义格式如下:

struct sembuf
{
    short sem_num; // 信号量的编号,默认为0
    short sem_op;  // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                   // 一个是+1,即V(发送信号)操作。
    short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,当进程结束之前,取消对信号量的任何操作
};

代码示例

#include 
#include 
#include 
#include 
//模拟用钥匙开锁场景

union semun//定义一个联合体
{
	    int              val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};

void pGetKey(int semid)
{
	struct sembuf set;//定义一个结构体
	set.sem_num = 0;//第一个信号量
	set.sem_op = -1;//拿走钥匙 数量-1
	set.sem_flg = SEM_UNDO;//拿不到钥匙就等待
	semop(semid,&set,1);//第二个参数为指针,这里需要以地址形式写入
	printf("get the key\n");
}

void vPutBackKey(int semid)
{
        struct sembuf put;
        put.sem_num = 0;
        put.sem_op = +1;//放回钥匙 数量+1
        put.sem_flg = SEM_UNDO;
        semop(semid,&put,1);
	printf("put back the key\n");
}

int main()
{
	int semid;
	int pid;

	key_t key;
	key = ftok(".",1);

	semid = semget(key,1,IPC_CREAT|0666);//以可读可写权限创建信号量
	
	union semun initsem;//调用联合体:初识化钥匙数量
    initsem.val = 0;//一开始钥匙数量为0

	semctl(semid,0,SETVAL,initsem);
	
	pid = fork();
	if(pid > 0)
	{
		pGetKey(semid);//拿钥匙 但钥匙数量为0 需等待钥匙放回 即等待子进程放回钥匙
		printf("this is father\n");
		vPutBackKey(semid);//拿到钥匙后使用完毕放回钥匙
	}
	else if(pid == 0)
	{
		printf("this is child\n");
		vPutBackKey(semid);//将钥匙放回,钥匙数量+1,父进程检测到有钥匙就拿钥匙,即先进入子进程再进入父进程
	}
	else
		printf("error!\n");
	
	return 0;
}

Linux进程通信——信号量_第1张图片

可见,一开始没有钥匙,父进程中没有任何操作,当子进程中将钥匙放回时,父进程检测到后拿到钥匙使用完毕后并放回。实现信号量的检测。

你可能感兴趣的:(Linux系统编程,linux)