文章目录
1、进程间通信介绍
进程间通信,IPC
那么为什么会有进程间通信呢?!?
- 原因: 在一个项目中,可能会被分为多个模块,在多个程序中实现,进而产生多个进程,故为其之间能协同工作提高效率,便有了进程间通信的需求!
- 进程之间,是无法直接进行通信的,原因在于各个进程都有其独立的虚拟地址空间,访问都是自己的虚拟地址
- 操作系统便提供了多种进程间通信方式,其本质都是一块公共的访问空间
常见的进程间通信方式有:管道,共享内存,消息队列,信号量~~~
下面将为铁子们,逐个进行讲解!!!
2、管道
2.1、管道的介绍
- 管道:实现数据传输
特性:
- 1. 半双工通信;(也可选择方向单向通信)
半双工通信:是指数据可以沿两个方向进程传输,但是同一时刻一个信道只准单向传送,有名双向交替通信
- 2. 提供字节流传输服务,具有先进先出的特性
- 3. 基于"连接"(所有读端关闭write继续异常,所有写段关闭继续read,返回0)
- 4. 生命周期随进程
- 5. 自带 同步与互斥
>>互斥:
资源在同一时间只有一个进程能访问;
管道的写入操作大小不超过PIPE_BUF大小时保证原子操作;
>>同步:
若管道中无数据,read阻塞、若管道数据满了,write阻塞;
- 本质:内核中的一块缓冲区,即也就是内核中的一块内存
- 分类: 匿名管道 和 命名管道
2.2、匿名管道和命名管道
2.2.1、匿名管道
2.2.1.1、匿名管道概念
- 匿名管道其没有标识符,
只能用于具有亲缘关系的进程间通信
;
- 那么为什么呢???
以父子进程为例,父进程创建子进程,子进程复制了父进程大部分的信息,其中就包括匿名管道的操作句柄,此时子进程便可通过该句柄操作匿名管道,实现与父进程的检测间通信
2.2.1.2、匿名管道操作
int pipe(int pipefd[2]);----创建一个匿名管道
pipefd[0]:用于从管道中读取数据
pipefd[1]:用于从管道中写入数据
返回值:成功返回0,失败返回-1
2.2.1.2、特性介绍
附:
阻塞:为了完成某个工作而发起的接口调用,当前若不具备不具备功能的条件,则会一直等待,不返回
- 1.若管道中无数据,read会默认阻塞,直至读取到数据后返回
- 2.若管道中数据写满,write会默认阻塞,直至数据读出,管道中有剩余的空间
- 3.当所有写端关闭(close pipefd[1],
注意是所有写端口
),则read读取完缓冲区中所有数据,继续read将不在阻塞而是返回0(返回0实则是告诉用户,继续read将没有意义~~) - 4.当所有读端关闭(close pipefd[0],
注意是所有读端
),则继续write数据,将会触发异常,退出程序
注:之所以强调所有读端和写端,是因为创建子进程后,父子进程都具有对管道的读写操作,即读端和写端
2.2.2、命名管道
2.2.2.1、命名管道概念
- 命名管道:内核中缓冲区具有表示符,标识符是一个可见于系统的特殊管道文件
- 多个进程,通过打开同一个管道文件,访问内核中的缓冲区
2.2.2.1、命名管道操作
创建管道:
1.可通过命令行的方式,创建管道
$ mkfifo filenamne
2.可通过接口
int mkfifo(const char* filename,mode_t mode)
参数:filename 管道文件名(带路径)
mode:管道文件的权限
返回值: 返回0,成功
返回-1---情况A:创建命名管道出错
---情况B:管道标识符文件已存在,无需重新创建
错误码:EEXIST
注:1.因而对命名管道的出错判断因为(令res接受返回值)
if(error!=EEXIST&&res==-1) true:才进行创建异常的操作
2.管道文件的创建,不代表命名管道的创建,当一个进程打开管道文件时,命名管道才真正创建
那么大家会好奇命令行创建管道文件的权限吗??
- 其在终端上创建,会默认去掉其可执行权限
2.2.2.1、命名管道打开规则
- 若管道文件,被只读打开,会阻塞,直到管道文件被以写方式打开
- 若管道文件,被只写打开,会阻塞,直到管道文件被以读方式打开
特性与匿名管道基本保持一致!!!
3、共享内存
3.1、概念及特点
- 共享内存: 实现进程间的数据共享
- 特征:进程间最快通信方式!!!!!
提问:那么为什么共享内存是进程间最快的通信方式呢???
原理:其申请了一块物理内存,需要进行数据共享的进程将该同一块物理内存映射到自己的虚拟空间,然后通过自己的虚拟地址直接进行访问
- 上图为,管道数据传输的一个过程,而共享内存之所以是最快的进程间通信方式,在于其少了两次数据由用户加载至内核空间的数据拷贝操作!!!!
原理如下图:
3.2、操作流程
- 创建和打开共享内存
- 将共享内存映射到虚拟地址空间上
- 共享内存的读写操作(例:memcpy,strcpy,printf)
- 解除映射关系
- 删除共享内存
3.2.1、对应接口的介绍
1.创建和打开
int shmget(key_t key, size_t size, int shmflg);
参数:
key: 标识符,多个进程通过同意该标识符,打开同一块共享内存
(注:也可默认设置为IPC_PRIVATE,即该共享内存无非被其他进程找到,
只能被具有亲缘关系的进程找到)
size: 共享内存的大小
shmnflag:打开方式&权限 打开方式---IPC_CREAT
返回值:
成功返回一个操作句柄(非负整数),失败返回-1
2.创建映射关系
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid: shmget函数返回的句柄
addr: 指定映射的首地址--通知置为NULL
(用户制定空间可能被使用,导致映射失败)
shmflg: 同shmget函数中的shmflg参数
SHM_RDONIY-只读,默认0-可读可写
返回值:
成功返回映射首地址,失败则返回(void*)-1;
3.解除映射关系
int shmdt(const void *shmaddr);
参数:
shmaddr:shmat映射的首地址
4.删除共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:shmget返回操作句柄
cmd:要对共享内存进行的操作 IPC-RMID-删除共享内存
buf:用于设置或获取共享内存的信息,不用则置空
返回值:成功返回0,失败返回-1(一般情况)
3.2.2、共享内存读写操作及特点介绍
3.2.2.1、具体详细代码
1.共享内存读操作
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
#include <string.h>
#define IPC_KEY 0X123456
int main()
{
//创建共享内存
int shmid=shmget(IPC_KEY,4096,IPC_CREAT|0664);
if(shmid<0)
{
perror("shmge error");
return -1;
}
//void* shmat(shmid,addr,shmflag);
//建立映射
void* shm_start=shmat(shmid,NULL,0);
if(shm_start==(void*)-1)
{
perror("shmat error");
return -1;
}
while(1)
{
printf("%s\n",shm_start);
sleep(1);
}
//解除映射
shmdt(shm_start);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
2.共享内存写操作
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
#include <string.h>
#define IPC_KEY 0X123456
int main()
{
//创建共享内存
int shmid=shmget(IPC_KEY,4096,IPC_CREAT|0664);
if(shmid<0)
{
perror("shmge error");
return -1;
}
//void* shmat(shmid,addr,shmflag);
//建立映射
void* shm_start=shmat(shmid,NULL,0);
if(shm_start==(void*)-1)
{
perror("shmat error");
return -1;
}
int i=0;
while(1)
{
sprintf(shm_start,"一天又快过去了,要加油呀!!-%d",i++);
sleep(1);
}
//解除映射
shmdt(shm_start);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
3.2.2.2、特性分析
1. 共享内存与管道不同,当共享内存中,无数据时,不会出现读取数据阻塞现象
2. 当共享内存读端关闭时候,写端仍然会继续写入数据,且不会触发异常
3.
附:
进程间通信资源的命令操作:
命令: ipcs --- 用于查看进程通信资源
-m 共享内存
-s 信号量
-q 消息队列
ipcrm --- 用于删除进程通信资源
a.当正常对共享内存进行读写操作时
b. 当Ctr+c退出读和写操作时
- 从此图可知,共享内存,其的生命周期不随所连接进程的状态,而是随内核
c. 当正常读写时。。。用ipcrm对共享内存删除
在此情况下,对读写操作进行关闭,即使共享内存的连接数变0
此时共享内存才被销毁!!!!
- 共享内存的删除,其实并不会立即删除共享内存,而是将共享内存标记为删除状态,拒绝后续的新建映射(即,将key值设置空,使得其他进程无法找到!!!),待映射连接数为0时,自动释放共享内存
4.消息队列(System V)
- 功能: 用于实现进程间数据传输(数据块传输)
- 本质: 内核中的一个优先级队列
- 原理: 多个进程通过访问同一个内核中的消息队列,通过向队列添加节点(写数据),获取节点(读数据),实现数据的传输
- 特性: 消息队列自带同步与互斥,生命周期随内核
5、信号量(System V)
功能: 实现同步与互斥
本质: 内核中的一个计数器
操作:
- P操作: 在获取和访问数据之前进行P操操作 ,对计数器进行判断,若计数器小于等同于0,进程则阻塞,计数-1,当计数器大于0,则计数-1,正确返回
- V操作: 在产生资源之后,进行V操作,计数+1,唤醒一个等待的进程
同步实现: 对资源进行计数,在获取资源之前进行P操作,在产生资源之后进行V操作
互斥实现:将计数器初始为1,表示当前资源只有1个(则此时进行P操作之后,其他进程则需要阻塞等待无法访问,实现互斥),之后在分别进行P操作和V操作
总结
- 本文介绍了进程间通信的四种方式,管道,共享内存,消息队列,信号量
- 后续也将也将更进POSIX版本的信号量详细内容
- 最后感谢各位老铁的点击,若文章有描述不准确的地方,也希望多多指教
砥砺前行,不负韶华!!
兄弟冲鸭!!!!