关于进程间的通信,有很多的方法可以实现。管道、FIFO、消息队列、信号量以及共享内存都可以提供进程间通信功能。本文主要介绍的内容是信号量以及共享内存的使用。
一、 几个概念
理解信号量以及共享内存的概念以及学习对应的接口函数的使用,需要对标识符以及键等概念有所了解。下面我们逐一介绍以上概念。
标识符:
在对文件进行读取和写入操作的时候,我们需要一个对应的文件描述符来作为所有操作的入口。同样的,对信号量和共享内存的操作也使用一个标识符,这个标识符是后续介绍的这两个进程间通信机制的大部分函数接口的参数。
这个标识符的取值和文件标识符的取值不太一样。文件标识符是取最小的可用数字的,而信号量和共享内存的标识符在创建和删除之后,下一次的取值始终是向上递增1的,直到达到系统允许的最大限制值之后,回到0重新开始。
键:
要使多个进程之间可以实现数据的通信,我们必然要提供一种独立于信号量或者共享内存本身的媒介,来作为进程之间通信的桥梁,而这个桥梁即是我们现在将要介绍的键。
任何程序在使用进程间通信机制之前都要先建立对应的IPC(inter process communication)变量,而在建立这些变量的时候,都需要提供一个键。通常我们使用系统提供的接口来产生键值:
#include
key_t ftok(const char *path, in id)
参数:path是系统中存在的一个文件名, id是项目的编号
返回值:若成功返回键值,出错返回(key_t)-1
二、 信号量
当我们在执行进程间通信的程序时,难免会遇到一些临界性的代码。比如有两个程序在访问同一个文件。一个程序向文件写数据,而另一个文件要从文件里读取数据。这个时候问题就出现了:我们必须保证一个程序在没有向文件写完所有数据之前,另一个程序不能读取这个文件里的数据。我们知道文件记录锁可以满足我们的要求,实现数据的同步。而我们将要介绍的信号量也是一种提供数据同步的机制。
2.1 定义
信号量其实是一个计数器,用于计算当前的资源是否可用。为了获得共享资源,并且保持数据的同步,我们可以这样来使用信号量:
1) 测试控制该资源的信号量
2) 若此信号量的值为正,则进程可以使用该资源。对信号量做减1操作,表示他占用了这个资源
3) 若此信号量为的值0,则表示该资源已被占用。进程挂起等待,直到占用资源的进程释放该资源,使信号量的值为正。此时进程被唤醒,又返回至第1)步
通常信号量的值可以取任意正值,代表有多少个共享的资源。但一般情况下,我只使用0和1两个值,分别代表某一个资源的占用与空闲,这也叫做二进制信号量。
信号量集是包含一个或多个信号量的集合,但通常我们只使用含有一个信号量的信号量集。
2.2 数据结构和接口
每个信号量在内核中都有一个无名的结构表示,他至少包括以下成员:
struct
{
unsigned short semval; //信号量的值
pid_t sempid; //最后一次操作信号量的进程的ID
unsigned short semncnt; //挂起的等待至信号量的值变为正的进程数
unsigned short semzcnt; //挂起等待至信号量的值为0的进程数
….
}
Linux系统为我们提供了一下接口操作使用信号量:
1) 获取信号量标识符
#include
int semget(key_t key, int nsems, int flag)
参数:key代表键值,可以用ftok接口产生
nsems代表信号量的数目,若创建信号量则必须指定nsems,若引用已存在的信号量则将nsems指定为0
flag:类似open参数里的flag标志。他低端的9比特是信号量的权限位,同时我们可以用该权限标志和IPC_CREAT标志作或操作,这样就可以创建一个新的信号量。当然若是该信号量已存在,也不会产生错误,该标志将会被忽略。
返回值:成功返回信号量标识符,失败返回0
2) 操作信号量
#include
int semctl(int semid, int semnum, int cmd, /*union semun*/)
参数:semid 信号量标识符
semnum:要操作的信号量,若是在信号集中则可以取0值nsems-1之间的任意值,若只有一个信号量则取0即可。
cmd:操作信号量的指令,系统提供了10种不同的指令提供用户操作信号量。这里我们只介绍两个最常用的指令,其余的指令可以查阅资料获得。
a) SETVAL:用来把一个信号量的值初始化为我们设定的值,这个值通过第四个参数semun的val成员提供。该成员的结构如下:
union semnu
{
int val;
stuct semid_ds *buf;
unsigned short *array;
};
b) IPC_RMID:用于删除一个不再使用的信号量。该删除操作是立即发生的。任何在使用该信号量的进程,将在下次试图对信号量操作时产生错误。
返回值:根据cmd的不同,返回不同的值。对于SETVAL和IPC_RMID这两个指令,成功返回0,失败返回-1.
3) 对信号量进行增减操作,在该操作是一个原子操作
#include
int semop(int semid, struct sembuf semoparray[ ], size_t nops)
参数:semid: 信号量标识符
semoparray[ ]:指向信号量操作数组的指针。该指针有如下结构:
struct sembuf
{
unsigned short sem_num; //信号量的编号
short sem_op;
//操作中改变的数值,+1表示释放资源,-1表示占用资源
short sem_flag;
/*通常设为SEM_UNDO,当系统在没有释放信号量的情况下退出进程,系统将自动释放进程占用的信号量*/
}
nops : 操作的信号数量
返回值:成功返回0,失败返回-1
2.3 信号量使用
系统提供的接口相对复杂,使我们不太清楚信号量的使用方法。下面我们将利用系统提供的接口编写信号量操作的简单接口,当然这个接口是针对二进制信号量的(只取0和1)。
- 1)创建信号量
- int creat_sem(void)
- {
- int semid