Linux进程间通信之信号量

众所周知,每个进程使用的数据是是它独有的,即使之前没有,它也会在自己的内存区域拷贝一个(这里不考虑共享内存机制)。

如果进程间需要通信,有共享资源时,信号,管道,消息队列,信号量,共享内存,这几种技术都可以帮我们。这里先介绍信号量

信号量定义:

为了防止出现因多个程序同时访问一个共享资源而引发的一些问题,我们需要任一时刻只有一个执行线程访问代码的临界区域。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它。

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。

这里二进制信号量是最常见的信号量,本文也重点讨论二进制信号量

工作原理:

信号量只能进行两种操作(等待和发送信号),即PV操作

P:如果信号量变量>=0,就将其减-,如果值为0,就挂起该进程的执行

V:如果有其他进程因等待信号量变量而被挂起,就将其恢复,如果没有,就将变量加-;

      通俗一点的说就是:如果一个进程执行P操作进入了临界区,变量变为0,而另一个进程执行P的时候发现变量为0,将被挂起。而第一个进程执行完了后执行V操作时发现第二个进程在等待该变量,就将其恢复,这时变量没变值。

函数:

1:semget函数:创建一个新信号量或者取消一个已有信号量

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

第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。


semget函数成功返回一个相应信号标识符(非零),失败返回-1.

2、semop函数:改变信号量的值

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

sem_id是由semget返回的信号量标识符,sembuf结构的定义如下:

struct sembuf{
    short sem_num;//除非使用一组信号量,否则它为0
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                    //一个是+1,即V(发送信号)操作。
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
                    //并在进程没有释放该信号量而终止时,操作系统释放信号量
};

 

3、semctl函数: 直接控制信号量信息

int semctl(int sem_id, int sem_num, int command, ...); 

如果有第四个参数,它通常是一个union semum结构,定义如下:

union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
};


 

这里举个进程使用信号量来通信的例子:  例子是两个相同的程序同时向屏幕输出字符,这个例子要同一时间只有一个进程输出字符

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>

union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short *arry;
};

static int sem_id = 0;

static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();

int main(int argc, char *argv[])
{
	char message = 'X';
	int i = 0;

	//创建信号量
	sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);

	if(argc > 1)
	{
		//程序第一次被调用,初始化信号量
		if(!set_semvalue())
		{
			fprintf(stderr, "Failed to initialize semaphore\n");
			exit(EXIT_FAILURE);
		}
		//设置要输出到屏幕中的信息,即其参数的第一个字符
		message = argv[1][0];
		sleep(2);
	}
	for(i = 0; i < 10; ++i)
	{
		//进入临界区
		if(!semaphore_p())
			exit(EXIT_FAILURE);
		//向屏幕中输出数据
		printf("%c", message);
		//清理缓冲区,然后休眠随机时间
		fflush(stdout);
		sleep(rand() % 3);
		//离开临界区前再一次向屏幕输出数据
		printf("%c", message);
		fflush(stdout);
		//离开临界区,休眠随机时间后继续循环
		if(!semaphore_v())
			exit(EXIT_FAILURE);
		sleep(rand() % 2);
	}

	sleep(10);
	printf("\n%d - finished\n", getpid());

	if(argc > 1)
	{
		//如果程序是第一次被调用,则在退出前删除信号量
		sleep(3);
		del_semvalue();
	}
	exit(EXIT_SUCCESS);
}

static int set_semvalue()
{
	//用于初始化信号量,在使用信号量前必须这样做
	union semun sem_union;

	sem_union.val = 1;
	if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
		return 0;
	return 1;
}

static void del_semvalue()
{
	//删除信号量
	union semun sem_union;

	if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
		fprintf(stderr, "Failed to delete semaphore\n");
}

static int semaphore_p()
{
	//对信号量做减1操作,即等待P(sv)
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = -1;//P()
	sem_b.sem_flg = SEM_UNDO;
	if(semop(sem_id, &sem_b, 1) == -1)
	{
		fprintf(stderr, "semaphore_p failed\n");
		return 0;
	}
	return 1;
}

static int semaphore_v()
{
	//这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = 1;//V()
	sem_b.sem_flg = SEM_UNDO;
	if(semop(sem_id, &sem_b, 1) == -1)
	{
		fprintf(stderr, "semaphore_v failed\n");
		return 0;
	}
	return 1;
}


结果如下:

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

 

可以发现X和 我输入的第一个参数0都是成对的打印的,这是因为p操作进入临界区后,另一个进程无法进入,只有当前进程再次打印一个字符并执行V操作后,另一个进程才能够进去。

为了验证信号量的作用,这里举个不用信号量的例子:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	char message = 'X';
	int i = 0;	
	if(argc > 1)
		message = argv[1][0];
	for(i = 0; i < 10; ++i)
	{
		printf("%c", message);
		fflush(stdout);
		sleep(rand() % 3);
		printf("%c", message);
		fflush(stdout);
		sleep(rand() % 2);
	}
	sleep(10);
	printf("\n%d - finished\n", getpid());
	exit(EXIT_SUCCESS);
}


结果:

Linux进程间通信之信号量_第2张图片

结果很明显了,0X并不是成对打印的。

你可能感兴趣的:(linux,通信,进程,信号量)