进程间通信的方式有:管道、信号量、消息队列、共享内存和socket套接字
管道、信号量、消息队列、共享内存适用于单机,即完成一台主机上的两个进程或多个进程之间的通信,socket套接字适用于网络通信,通信的进程可以运行在不同的主机上。
无名管道:无名管道是一种半双工通信方式,数据只能单方向流动,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常指父子进程关系。
有名管道:有名管道也是一种半双工通信方式,适用于无亲缘关系的进程间通信。
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源,因此主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列:消息队列是消息的链表,存放在内核中并有消息队列标识符标识。消息队列克服了信号传递信息量少,管道只能承载无格式字节流以及缓冲区受限等缺点。
共享内存:共享内存是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建但是多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
套接字(socket):套接字也是一种进程间通信机制,与其他进程不同的是它可以用于不同主机间的进程通信。
本文对信号量进行讨论。
信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源的访问,临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源,这里的资源可以是一段代码、一个变量或这某种硬件资源。信号量的值大于0表示当前可用资源的数量,进程可以获取到资源并使用,若值小于0,其绝对值表示等待使用该资源的进程个数,进程将会阻塞等待有其他进程释放资源。
下面来看几个概念:
临界区:访问临界资源的代码区域
原子操作:不能被中止,不能被暂停的操作,一旦开始操作,必须等待其结束
p操作:申请临界资源,若申请成功,对临界资源的个数-1
V操作:释放临界资源,若释放成功,对临界资源的个数+1
阻塞、非阻塞关注的是进程在发出请求后的状态
阻塞:发出请求后,如果请求不能立即响应,则进程被挂起,等待请求响应
非阻塞:发出请求后,无论请求是否响应,进程都将继续向下执行
同步、异步关注的是进程发出请求后,进程是如何获知请求的条件已经发送
同步:发出请求后,如果请求不能立即响应,则不断去探测请求的条件是否已经发生
异步:发出请求后,进程继续往下执行,但是在请求的条件发生后,会通过内核消息通知机制,通知该进程已经发生
与消息队列类似,Linux内核也为每个信号量维护一个semid_ds数据结构。
struct semid_ds
{
struct ipc_perm sem_perm;
__kernel_time_t msg_otime;
__kernel_time_t msg_ctime;
struct sem *sembase;
struct sem_queue sem_pending;
struct sem_queque **sem_pending_last;
struct sem_undo *undo;
ushort sem_nsems;
};
sem_perm:对信号进行操作的许可证
msg_otime:对信号进行操作的最后时间
msg_ctime:对信号进行修改的最后时间
sembase:指向第一个信号
sem_pending:等待处理的挂起操作
sem_pending_last:最后一个正在挂起的操作
undo:撤销的请求
sem_nsems:数组中的信号量
1、信号量的创建或获取
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
key:由ftok函数得到的键值
nsems:要创建的信号集包含的信号个数,nsems为0表示只是打开信号集
semflg:操作标志,可取以下两个值:
IPC_CREATE:调用semget函数时,它会将此值与系统中其他信号集的key进行对比,如果存在相同的key,说明信号集已经存在,此时返回该信号集的标识符,否则新建一个信号集并返回其标识符。
IPC_EXCL:该宏必须和IPC_CREATE一起使用,否则没有意义,当semflg取IPC_CREATE | IPC_EXCL时,表示如果发现信号集已经存在,则返回错误。
该函数执行成功返回一个信号集的标识符,失败返回-1。
2、信号量的操作
信号量的操作与临界资源的使用情况有关,若值大于0表示当前可用资源的数量,若值小于0,其绝对值表示等待使用该资源的进程个数,信号量的值仅能由PV操作来改变。
#include <sys/sem.h>
int semop(int semid,struct sembuf *sops,size_t nsops);
semid:信号集的标识符
sops:指向进行操作的结构体数组的首地址,该结构体sembuf如下所示:
#include <sys/sem.h>
struct sembuf
{
ushort sem_num;//信号在信号集中的索引
short sem_op;//操作类型
short sem_flg;//操作标志
};
nsops:将要进行操作的信号的个数
该函数调用成功返回0,失败返回-1。
3、信号集的控制
使用信号量时,往往需要对信号集进行一些控制操作,比如删除信号集、对内核维护的信号集的数据结构semid_ds进行设置、获取信号集中的信号值等。
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd,...);
semid:信号集的标识符
semnum:标识一个特定的信号
cmd:控制操作的类型
...:可选参数,它依赖于第三个参数cmd,通过联合体变量semun选择要操作的参数
union semun
{
int val;//设置某个信号的值等于val
struct semid_ds *buf;//用于IPC_STAT和IPC_SET操作,存取semid_ds结构
ushort *array;//用于SETALL和GETALL操作
struct seminfo *buf;//为控制IPC_INFO提供的缓存
void *pad;//
};
/*
IPC_STAT:通过semun联合体中的buf返回当前的semid_ds结构体
IPC_SET:对信号集的属性进行设置
IPC_RMID:把semid指定的信号集从系统中删除
SETALL:设置信号集中所有信号的值
GETALL:返回信号集中所有信号的值
*/