Linux 进程间通信~信号量

信号量

(1)什么是信号量

当线程运行在多进程系统上时,不可避免的会产生临界区域,如果不加控制会产生不可预计的后果。为了防止多个程序同时访问同一资源,由避免CPU资源的浪费,定义了一种特殊的变量,它只能取正整数值,并且程序对其访问都是原子操作。这样就保证了进程在对临界资源访问时的安全性。
最简单的信号量只能取0和1两个值,即二进制信号量。二进制信号量的操作被叫做PV操作,P操作就是信号量变量的值大于0,就给它减去0;如果它的值等于0,就挂起该进程。V操作就是如果有其它进程因为等待信号量而被挂起,就让它恢复运行,如果没有进程因为它而被挂起,就给它加1。

(2)信号量的实现

用到了一下几个函数
#include <sys/sem.h>

int semget(key_t key, int num_sems, int sem_flags);

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

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

其中,key的作用很像一个文件名,它代表程序可能要使用某个资源,如果多个程序使用相同的key值,它将负责协调工作。

semget函数的作用是创建一个新的信号量或者获取一个已有信号量的key值。创建成功则返回一个非0值,即信号量标识符,否则返回-1。
semop函数用于改变信号量的值。它是利用一个结构体来设置信号量的值。结构体如下所示
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
}
其中,sem_num是信号的编号,一般为0。sem_op是在一次操作中需要改变的值,通常只会用到两个值,一个是-1,也就是P操作,它等待信号量变为可用。一个是+1,也就是V操作,它发送信号表示信号量现在可用。
最后一个成员sem_flg通常被设置为SEM_UNDO,这样可以让系统追踪该信号量,若进程在没有释放信号量的情况下终止,则系统会自动释放进程所持有的信号量。
semctl函数用来直接控制信号量信息。常用来对信号量进行初始化和删除。

代码示例

信号量常用于进程间控制,为了提高代码的重用性,将函数封装为P操作和V操作。代码如下所示:
头文件定义了union semun结构

#ifndef __SEM_H
#define __SEM_H

typedef union SemUn
{
	int  val;
}SemUn;

int  SemGet(int key,  int semVal[],  int nsems);

int  SemP(int  semid, int  index[],  int  sems);

int SemV(int semid,  int index[],  int  sems);

int  SemDel(int semid);


#endif

#include "sem.h"

#include <sys/sem.h>
#include <malloc.h>
#include <string.h>
#include <assert.h>


int SemGet(int key, int semVal[],  int nsems)
{
	int semid = semget((key_t)key,  nsems,  0664);
	if(semid == -1)
	{
		semid = semget((key_t)key,  nsems,  0664|IPC_CREAT);
		assert(semid != -1);

		int i = 0;
		for(; i < nsems; ++i)
		{
			SemUn un;
			un.val = semVal[i];

			semctl(semid, i, SETVAL, un);
		}
	}

	return semid;
}

int SemP(int semid, int index[], int sems)
{
	struct sembuf *buf = (struct sembuf*)malloc(sizeof(struct sembuf) * sems);
	assert(buf != NULL);

	int i = 0;
	for(; i < sems; ++i)
	{
		buf[i].sem_num = index[i];
		buf[i].sem_op = -1;   //   P
		buf[i].sem_flg = SEM_UNDO;
	}

	if(-1 == semop(semid,  buf,  sems))
	{
		free(buf);
		return -1;
	}

	free(buf);

	return 0;
}

int SemV(int semid, int index[], int sems)
{
	struct sembuf *buf = (struct sembuf*)malloc(sizeof(struct sembuf) * sems);
	assert(buf != NULL);

	int i = 0;
	for(; i < sems; ++i)
	{
		buf[i].sem_num = index[i];
		buf[i].sem_op = 1;   //   P
		buf[i].sem_flg = SEM_UNDO;
	}

	if(-1 == semop(semid,  buf,  sems))
	{
		free(buf);
		return -1;
	}

	free(buf);
	return 0;
}

int SemDel(int semid)
{
	return semctl(semid, 0, IPC_RMID);
}

利用fork创建进程来实现父进程与子进程的交替执行。

/*************************************************************************
    > File Name: fork.c
    > Author: wangchen
    > Mail: [email protected] 
    > Created Time: Sat 23 Mar 2019 05:16:56 PM CST
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include "sem.h"

int main()
{
	int semVal = 1;
	int semid = SemGet(1234, &semVal, 1);
	printf("semid = %d\n", semid);
	pid_t pid = fork();
	assert(pid != -1);

	if(pid == 0)
	{
		int i = 0;
		for(; i < 10; ++i)
		{
			int index = 0;
			SemP(semid, &index, 1);
			printf("A\n");
			sleep(1);
			SemV(semid, &index, 1);
		}
	}
	else
	{
		int i = 0;
		for(; i < 10; ++i)
		{
			int index = 0;
			SemP(semid, &index, 1);
			printf("B\n");
			sleep(1);
			SemV(semid, &index, 1);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/ichliebecamb/article/details/88896430