首页 > 代码库 > Linux进程间通信-信号量

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高性能server编程》 第13章 多进程编程/信号量

        2、Linux多进程编程

Linux进程间通信-信号量