进程间通信:
(种类:数据传输、数据共享、进程控制、事件通知)
基本介绍:干什么、如何通信、为何这么复杂
干什么:进程间进行交流(数据传输、数据共享、进程间的控制、通知事件)
数据传输:一个进程需要将它的数据发送给另一个进程
数据共享:多个进程间共享同一份资源
进程控制:有些进程希望王权控制另一个进程的执行(如debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件
如何通信:因为进程的独立性,因此通信需要双方拥有公共的媒介才能通信,而这个媒介由操作系统提供
因为通信场景不同,因此操作系统也提供多种不同的进程间通信方式
进程间通信方式: 发展:管道-》SystemV进程间通信-》POSIX进程间通信
管道:匿名管道pipe、命名管道
System V :管道、共享内存、消息队列、信号量
POSIVX:管道、共享内存、消息队列、信号量、互斥量、条件量、读写锁
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void niming()
{
int fds[2];
char buf[100];
int len = 0;
if (pipe(fds) == -1)
perror("make pipe"),exit(1);
while (fgets(buf, 100, stdin))
{
len = strlen(buf);
if (write(fds[1], buf, len) != len)
{
perror("write to pipe");
break;
}
memset (buf, 0x00, sizeof(buf));
if ((len = read(fds[0], buf, 100)) == -1)
{
perror("read from pipe");
break;
}
if (write(1, buf, len) != len)
{
perror("write to stdout");
break;
}
}
return;
}
int main()
{
niming();
return 0;
}
管道:即进程间所需要的那个共同媒介
匿名管道/命名管道—半双工单向通信(可选择的单向通信),需要双方通信时,需要建立起俩个通道
(全双工(Full Duplex)是指在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。 半双工(Half Duplex),所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台。单工即表示只允许只有一种动作,就相当于单向行驶一样。)
特性:半双工、生命周期随进程、自带同步与互斥《PIPE——BUF》
同步:对临界资源访问的时序可控
互斥:对临界资源访问的唯一
临界资源:大家都能访问的公共资源
原理:操作系统在内核创建一块缓冲区,并且为用户提供管道的操作句柄(文件描述符)
用户通过IO接口实现管道的操作;描述符共有俩个一个用于读取数据,一个用于写数据
因为匿名管道是匿名的,其他进程在内核中无法找到这个管道,因此匿名管道实现进程间通信,只能用于具有亲缘关系的进程(子进程用过复制父进程pcb–得到管道的操作句柄(俩个文件描述符)
管道读写特性:
若管道中没有数据,那么如果read读取数 据,则会阻塞,直到读取到数据返回
若管道中数据满了。那么如果继续write写入数据 则会阻塞 知道能够写入数据
如果所有读端都关闭了,这时候write写入数据则会出发异常–SIGPIPE (导致程序退出)
如果所有写端都关闭了,这时候read读取数据 将数据读完后继续读则会返回0—管道创建成功后,若没有用到管道的某一端,不久这一端关闭掉
管道符的实现:链接俩个命令,将第一个命令的输出结果作为第二个命令的输入数据进行处理 ls | grep make
主进程 创建俩个子进程,在俩个中分别程序替换让俩个子进程分别运行ls程序和grep程序 ls程序是浏览目录,将结果写入标准输出
Grep程序是从标准输入读取数据(循环读取数据),进行过滤
命名管道:
如果我们想在不相关的进程之间交换数据,可以使⽤FIFO⽂件来做这项⼯作,它经 常被称为命名管道。
命名管道是⼀种特殊类型的⽂件
创建命名管道:
1.创建命令行: mkfifo filename
2.函数创建:int mkfifo(const char* filename, mode_t mode)
与匿名管道的区别:
1.匿名管道由pipe函数创建并打开
2.命名管道由mififo函数创建,打开用open
3.FIFO(命名管道)与pipe(匿名管道)之间唯⼀的区别在它们创建与打开的⽅式不同,一但这些 ⼯作完成之后,它们具有相同的语义
打开规则:
- 如果当前打开操作是为读⽽打开FIFO时
4.O_NONBLOCK disable:阻塞直到有相应进程为写⽽打开该FIFO
5.O_NONBLOCK enable:⽴刻返回成功
6.如果当前打开操作是为写⽽打开FIFO时
7.O_NONBLOCK disable:阻塞直到有相应进程为读⽽打开该FIFO
8.O_NONBLOCK enable:⽴刻返回失败,错误码为ENXIO
若管道以读写打开,则不会阻塞
共享内存:
是进程间通信方式中最快的一种;(没有同步与互斥)
通信原理(为什么最快): 进程不在通过执行进入内核的系统调用来传递彼此的数据在物理内存中开辟一块空间,并且将空间映射到各个进程的虚拟地址空间,这时候进程就可以通过虚拟地址直接对内存进行操作,相较于其他通讯方式(将数据拷贝内核态,用的时候从内核态拷贝到用户态)少了俩步用户态/内核态之间的数据拷贝过程,因此最快。
消息队列:
消息队列提供了⼀个从⼀个进程向另外⼀个进程发送⼀块数据的⽅法
每个数据块都被认为是有⼀个类型,接收者进程接收的数据块可以有不同的类型值
消息队列也有管道⼀样的不⾜,就是每个消息的最⼤⻓度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)
消息队列函数,用来创建和访问一个消息队列
Int msgget(key_t key, int msgflg);
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该消息队列的标识码;失败返回-1
消息队列控制函数:
Int msgctl(int msqid, int cmd, struct msqid_ds* buf)
Msqid:由msgget函数返回的消息队列标识码
Cmd:是将要采取的动作:
把⼀条消息添加到消息队列中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是⼀个指针,指针指向准备发送的消息,
msgsz:是msgp指向的消息⻓度,这个⻓度不含保存消息类型的那个long int⻓整型
msgflg:控制着当前消息队列满或到达系统上限时将要发⽣的事情
msgflg=IPC_NOWAIT表⽰队列满不等待,返回EAGAIN错误。
返回值:成功返回0;失败返回-1
是从⼀个消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是⼀个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息⻓度,这个⻓度不含保存消息类型的那个long int⻓整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发⽣的事
返回值:成功返回实际放到接收缓冲区⾥去的字符个数,失败返回-1
信号量:主要用于同步与互斥
互斥:由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间使用这些资源,进程的这种关系为进程互斥
系统中某些资源依次只允许一个进程使用,称这样的资源为临界资源或互斥资源
在进程中涉及到互斥资源的程序段叫临界区
信号量和P、V原语
信号量:
互斥:p、v在同一个进程中,
同步:p、v在不同进程中
信号量值含义:
S>0: S表示可用资源的个数
S=0:表示无可用资源,无等待进程
S<0:|s|表示等待队列中的进程个数
信号量本质上是⼀个计数器
struct semaphore
{
int value;
pointer_PCB queue;
}
功能:⽤来创建和访问⼀个信号量集
原型
int semget(key_t key, int nsems, int semflg);
参数
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该信号集的标识码;失败返回-1
int semctl(int semid, int semnum, int cmd, …);
参数
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有三个可取值)
最后⼀个参数根据命令不同⽽不同
返回值:成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向⼀个结构数值的指针
nsops:信号量的个数
返回值:成功返回0;失败返回-1