您的位置:首页 > 运维架构 > Linux

【Linux】进程间通信——信号量

2018-03-26 23:12 316 查看

1. 信号量的基本概念

信号量主要用于同步与互斥的,先简单的说几个概念吧

原子性:表示一个事件的两种状态,要么做了,要么没有做,没有第三种状态;

同步:在多道程序环境下,进程是并发执行的,不同进程之间存在着不同相互制约的关系;

互斥:在一段时间内,资源只允许被一个进程访问;

临界资源:像打印机这类一次只允许一个进程使用的资源;

临界区:多个进程访问临界资源的那一段代码。

  那么什么是信号量呢,我们可以简单的理解成计数器(当然实际上并没有那么简单)描述资源的多少,它本身不具备数据交换的功能,而是通过控制临界资源来实现进程间通信。

  简单说下信号量的工作机制,我么说信号量可以理解为一个计数器,它有一个初值(>0),每当有进程申请使用信号量时,通过P操作来对信号量进行-1;当计数器减到0的时候,其他进程想要访问资源,就需要挂起等待,直到该进程执行完操作,通过V操作对信号量+1释放资源。所以,我们说信号量也是进程间通信的一种方式,比如互斥锁的简单实现就是二元信号量,一个进程在使用互斥锁时,通知其他进程,阻止他们的访问挂起等。举一个例子:假如我们一趟火车只有一张火车票了,但是有两个人都要买,其中一个人先申请到了资源,买到了票,那么另一人只能等,等到如果有人退票或者改签,他就可以买到票了。

  

2.信号量的操作函数

信号量的操作函数与消息队列也是十分类似的:

semget

功能:用来创建和访问一个信号

原型:

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


参数:

key:信号集的名字,与消息队列类似,不在详细的说了

nsems:信号集中信号量的个数,我们使用semget这个函数创建的是一个信号集,包括了好多信号量的, 当然可以选择只创建一个

semflg: 两个参数IPC_CREAT和IPC_EXCL,与之前的消息队列也类似-

返回值:成功返回⼀一个⾮非负整数,即该信号集的标识码;失败返回-1

semctl

功能:⽤用于控制信号量集

原型:

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


参数:

semid:由semget返回的信号集标识码

semnum:信号集中信号量的序号,序号从0开始

cmd:将要采取的动作(有三个可取值)

最后⼀一个参数根据命令不同⽽而不同

返回值:成功返回0;失败返回-1

  这个函数,我们初始化和删除都会用到它,当第三个参数为IPC_RMID时,用于删除信号集;当第三个参数设置为SETVAL时,可以用于信号量的初始化,但此时就需要第四个参数了;第四个参数是这样的,它需要加入一个联合体:

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 */
}


semop

功能:⽤来实现PV操作的,这个函数还用得到了一个结构体

原型:

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


参数:

semid:是该信号量的标识码,semget函数的返回值

sops:指向一个结构体的指针

nsops:信号量的个数

返回值:成功返回0;失败返回-1

这个结构体是这样的:

struct sembuf
{
short sem_num;//sem_num是信号量的编号
short sem_op;//sem_op是信号量⼀次PV操作时加减的数值,一般只会⽤用到两个值:一个是“-1”,也就是P操作,等待信号量变得可⽤;另⼀个是“+1”,也就是我们的V操作,发出信号量已经变得可⽤
short sem_flg;//sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO
};


同消息队列类似,这里也可以使用ipcs -s查看IPC资源,使用ipcrm -s删除IPC资源。



  这里需要注意的是:信号量与信号并不一样,有的同学可能会把这两个概念搞混了,信号量是用来操作系统进程间同步访问共享资源。信号是用来通知进程发生了异步事件,虽然二者名字上很相似,但本质上相差很大。

  

3.实例

下面我们就用一段很经典的代码使用这些函数:

先写一个Makefile

sem_test:sem_test.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f sem_test


comm.h

#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
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*/
};
int createSemSet(int nums);
int initSem(int semid, int nums, int initval);
int getSemSet(int nums);
int P(int semid, int who);
int V(int semid, int who);
int destorySemSet(int semid);
#endif


comm.c

#include "comm.h"
static int commSemSet(int nums, int flags)
{
key_t key = ftok("/tmp", 0x6666);
if(key < 0)
{
perror("ftok");
return -1;
}
int semid = semget(key, nums, flags);
if(semid < 0)
{
perror("semget");
return -2;
}
return semid;
}
int createSemSet(int nums)
{
return commSemSet(nums, IPC_CREAT|IPC_EXCL|0666);
}
int getSemSet(int nums)
{
return commSemSet(nums, IPC_CREAT);
}
int initSemS
c22b
et(int semid, int nums, int initval)
{
union semun _un;
_un.val = initval;
if(semctl(semid, nums, SETVAL, _un) < 0)
{
perror("semctl");
return -1;
}
return 0;
}
static int commPV(int semid, int who, int op)
{
struct sembuf _sf;
_sf.sem_num = who;
_sf.sem_op = op;
_sf.sem_flg = 0;
if(semop(semid, &_sf, 1) < 0)
{
perror("semop");
return -1;
}
return 0;
}
int P(int semid, int who)
{
return commPV(semid, who, -1);
}
int V(int semid, int who)
{
return commPV(semid, who, 1);
}
int destorySemSet(int semid)
{
if(semctl(semid, 0, IPC_RMID) < 0)
{
perror("semctl");
return -1;
}
return 0;
}


test.c

#include "comm.h"
int main()
{
int semid = createSemSet(1);
initSemSet(semid, 0, 1);
pid_t pid = fork();
if(pid == 0)
{
//child
int _semid = getSemSet(0);
while(1)
{
P(_semid, 0);
printf("A");
fflush(stdout);
usleep(123456);
printf("A ");
fflush(stdout);
usleep(123456);
V(_semid, 0);
}
}
else
{
//parent
while(1)
{
P(semid, 0);
printf("B");
fflush(stdout);
usleep(123456);
printf("B ");
fflush(stdout);
usleep(123456);
V(semid, 0);
}
wait(NULL);
}
destorySemSet(semid);
return 0;
}


看上面的测试程序,使用fork()创建了两个进程,但是两个进程交替执行,所以如果没有使用PV操作的话,打印出来的是酱紫的:



但是如果我们加了PV操作,这样就可以保证A和B是成都存在的了

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息