什么是进程间通讯?
进程间通讯是一种机制,操作系统进程和线程通过它交换数据和消息。IPC 包括本地机制(如 Windows 共享内存)或网络机制(如 Windows 套接字)
进程间的通信方式:管道、信号量、消息队列、共享内存、套接字
一、管道
把一个进程连接到另一个进程的一个数据流称为一个“管道”,通常是用作把一个进程的输出通过管道连接到另一个进程的输入。管道本质上是内核的一块缓存。
管道分为有名管道和无名管道。
1.有名管道(命名管道):在任意两个进程间的通信。
有名管道的创建:
#include
#include
int mkfifo(const char *filename,mode_t mode);
有名管道也被称为FIFO文件,是一种特殊的文件。由于linux所有的事物都可以被视为文件,所以对命名管道的使用也就变得与文件操作非常统一。
有名管道的特征:
a、有名字,储存于普通文件系统中
b、任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符
c、跟普通文件一样,用read()和writ()来读和写
d、不能用lseek()来定位
e、具有写入原子性,支持多写者同时进行写操作而数据不会相互践踏
2.无名管道:只能在父子进程间的通信。
无名管道的创建:
函数原型: int pipe(int pipefd[2]);
pipefd[2] :无名管道的两个文件描述符,int型的数组,大小为2,pipefd[0]为读端,pipefd[1]为写端
#include
int pipe(int pipefd[2])
无名管道的缺点:
a、不能使用open()打开
b、只能用于父子进程间通信
c、半双工工作方式,读写端是分开的,pipefd[0]为读端,pipefd[1]为写端
d、写入操作不具有原子性,因此只能用于一对一的简单通信
e、不能用lseek()来定位
※在程序中,管道有固定的读端和写端,管道为空,read()会阻塞,管道为满,write()会阻塞。在操作管道时,要求读端和写端都存在。写端关闭时,read()直接返回,返回值为0。读端关闭时,write()产生异常,收到SIGPIPE信号
二、信号量
它是一个特殊的变量,只允许对它进行等待(wait)和发送信号(signal)这两种操作。
信号量是用来解决线程间同步或互斥的一种机制,也是一个特殊的变量,变量的值代表着当前可以利用的资源。如果等于0,那就意味着现在没有资源可用。根据信号量的值可以将信号量分为二值信号量和计数信号量。
二值信号量: 值取0或者1
计数信号量: 值可以大于1
信号量具有原子操作的特性,所谓原子操作就是不能被更高等级中断抢夺优先的操作。
由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。
P操作:如果有可用的资源(信号量>0),那么占用一个资源(信号量-1)。如果没有可用的资源(信号量=0),则进程被阻塞,直到系统重新给他分配资源。
V操作:如果在该信号量的等待队列中有进程在等待该资源,则唤醒一个进程,否则释放一个资源(信号量+1)
三、共享内存
共享内存允许两个不相关的进程访问同一个逻辑内存(如图所示)。共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式。
用命令 ipcs可以查看进程间通信的共享内存的使用情况 (也可以查看信号量、消息队列的使用情况)
ipcrm命令可以移除一个消息对象。或者共享内存段,或者一个信号集,同时会将与ipc对象相关链的数据也一起移除。当然,只有超级管理员,或者ipc对象的创建者才有这项权利。
但共享内存的使用会面临同步机制,需要与其它通信方式配合,最合适的就是信号,当然,互斥锁也可以解决这个问题。
两个进程同步:对临界资源的访问有控制能保证同一时刻只有一个进程访问,则这两个进程是同步的。
那什么是同步,什么是异步?
同步:所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一-旦调用返回,就得
到返回值了。换句话说,就是由调用者主动等待这个调用的结果。
异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当-个异步过程调用发出
后,调用者不会立刻得到结果。而是等被调用者准备好数据后反过来通知调用者。
阻塞和非阻塞:
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。阻塞是指方法(函数)执行后,在结
果返回之前,当前线程(进程)会被挂起。调用线程(进程)只有在得到结果之后才会返回。
非阻塞方法(函数)指在不能立刻得到结果之前,该方法(函数)不会阻塞当前线程,可以立即返回,只是返
回结果是失败,没有数据,-般需要cpu周期性轮询,以检查数据是否就绪。
简单点理解就是:
1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。
2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
3. 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
4. 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
四、消息队列
消息队列与有名管道有许多相似之处。消息队列提供了一个从一个进程向另一个进程发送数据块的方法,每个数据块都可以被认为是有一个类型,接受者接受的数据块可以有不同的类型。虽然我们可以通过发送消息来几乎完全避免同步或阻塞的问题, 但是同管道类似,它有一个不足就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数(MSGMNB),系统上消息队列的总数上限(MSGMNI)。可以用cat /proc/sys/kernel/msgmax查看具体的数据。
消息队列用到的函数
1.msgget函数:创建和访问一个消息队列
返回值:成功时msgget函数返回-一个正整数,即队列标识符,失败时返回-1。
#include
#include
#include
int msgget(key_t key, int msgflag);
2.msgsnd函数 :把消息添加到消息队列中
#include
#include
#include
int msgsnd(int msqid, const void *msg_ptr, size_t msgsz, int msgflg);
第一个参数msqid是由msgget函数返回的消息队列标识符。
第二个参数msg_ptr是一个指向准备发送消息的指针,消息必须像刚才说的那样以-一个长整型成员变量开始。
第三个参数是msg_ ptr指向的消息的长度。这个长度不能包括长整型消息类型成员变量的长度。
第四个参数msgf1g控制在当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。如果msgf1g中设置IPC_NOWAIT标志,函数将立刻返回,不发送消息并且返回值为-1。如果msgf1g中的IPC_ NOWAIT标志被清除,则发送进程将挂起以等待队列中腾出可用空间。
返回值:成功时这个函数返回0,失败时返回-1。如果调用成功,消息数据的一份副本将被放到消息队列中。
3.msgrcv函数:从一个消息队列中获取消息
#include
#include
#include
ssize_t msgrcv(int msqid, void *msg_ptr, size_t msgsz, long msgtype, int msgflg);
第一个参数msqid是由msgget函数返回的消息队列标识符。
第二个参数msg_ ptr是一个指向准备接收消息的指针,消息必须像前面msgsnd函数中介绍的那样以一个长整型成员变量开始。
第三个参数是msg_ ptr指向的消息的长度,它不包括长整型消息类型成员变量的长度。
第四个参数msgtype是一个长整数,它可以实现一种 简单形式的接收优先级。如果msgtype的值为0,就获取队列中的第一个可用消息。如果它的值大于零,将获取具有相同消息类型的第一个消 息。如果它的值小于零,将获取消息类型等于或小于msgtype的绝对值的第一个消息。
第五个参数msgf1g用于控制当队列中没有相应类型的消息可以接收时将发生的事情。如果msgflg中的IPC_ NOWAIT标志被置,函数将会立刻返回,返回值是-1。如果msgflg中的IPC_ NOWAIT标志被清除,进程将会挂起以等待--条相应类型的消息到达。
返回值:成功时msgrcv函数返回放到接收缓存区中的字节数,消息被复制到由msg_ ptr 指向的用户分配的缓存区中,然后刪除消息队列中的对应消息。失败时返回-1。
4.msgctl函数:消息队列的控制函数
#include
#include
#include
int msgctl(int msqid, int command, struct msqid_ds *buf);
第一个参数msqid是由msgget返回的消息队列标识符。
第二个参数command是将要采取的动作。它可以取3个值,如表1所示。
命令 | 说明 |
IPC_STAT | 把msqid_ds结构中的数据设置为消息队列的当前关联值 |
IPC_SET | 如果进程有足够的权限,就把消息队列的当前关联值设置为msqia ds结构中给出的值 |
IPC_RMTD | 删除消息队列 |
返回值:成功时它返回0,失败时返回-1。如果删除消息队列时,某个进程正在msgsnd或msgrev函数中等待,这两个函数将失败。
五、套接字
套接字(socket) 是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。
套接字的特性由3个属性确定,它们分别是:域、类型和协议。
1、套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_ INET,它指的是Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。其底层的协议一网际协议 (IP)只有一个地址族,它使用-种特定的方式来指定网络中的计算机,即人们常说的IP地址。
2.套接字类型
一个 套接字域可能有多种不同的通信方式,而每种通信方式又有其不同的特性。但AF_ UNIx域的套接字没有这样的问题,它们提供了一个可靠的双向通信路径。在网络域中,我们就需要注意底层网络的特性,以及不同的通信机制是如何受到它们的影响的。因特网协议提供了两种通信机制:流(stream)和数据报(datagram)。它们有着截然不同的服务层次。
流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现,同时也是AF_UNIX中常用的套接字类型。
流套接字提供的是一个有序、可靠、双向字节流的连接,因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。
与流套接字相对的是由类型SOCK_DGRAM指定的数据报套接字,它不需要建立连接和维持一个连接,它们在AF_INET中通常是通过UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和维持一个连接。
3、套接字协议
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。通常只需要使用默认值即可。