进程间通信(IPC)
进程之间无法直接通信,进程之间具有独立性,因为每个进程都有其独立的虚拟地址空间,虚拟地址空间的不同导致映射方式的不同,导致无法访问同样的物理空间所以无法直接通信;
进程间通信的本质就是为进程之间提供一块公共区域;
进程间通信方式:管道、共享内存、消息队列、信号量等;
ipcs用于查看进程间通信资源,-m共享内存,-s信号量,-q消息队列;
ipcrm用于删除进程间通信资源;
管道
#include <unistd.h>
int pipe(int pipefd[2]);
原理及本质:
内核中的一块缓冲区(内核中的一块内存);
特性:
①**pipefd[0]**用于读数据,若管道中没有数据,read默认阻塞,直到读取到数据后返回;
②**pipefd[1]**用于写数据,如果管道中数据写满,write默认会阻塞,直到数据被读出后有空闲空间;
③若管道所有读端被关闭,write继续写入数据则会触发异常,退出程序;
④若管道所有写端被关闭,read读取完缓冲区数据后,继续read将不再阻塞而是直接返回0;
⑤半双工通信(可以选择方向的单向通信,但是一般不会切换方向);
⑥管道中的数据传输是一种基于链接、先入先出、自带同步与互斥的字节流传输;
⑦生命周期随进程。
匿名管道:
内核中的缓冲区没有标识符,只能用于具有亲缘关系的进程间通信,以创建进程时子进程复制父进程的方式来复制管道句柄来访问管道,并且不局限于父子进程之间;
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int filedes[2];
char buffer[80];
pipe(filedes);
if(fork() > 0)
{
char s[] = "hello!\n";
write(filedes[1], s, sizeof(s));
}
else
{
read(filedes[0], buffer, 80);
printf("%s", buffer);
}
}
命名管道:
内核中的缓冲区具有标识符,标识符是一个可见于文件系统的特殊管道文件(就算删除也只是删除了标识符,缓冲区不会立刻被释放),多个进程通过打开同一个管道文件来访问同一块内核中的缓冲区(当文件还未打开时并没有在内核中开辟缓冲区,只有进程开始打开文件时才会开辟);
命名管道打开特性:
如果管道以只读被打开,会阻塞,直到管道文件也以写的方式被打开;
如果管道以只写被打开,会阻塞,直到管道文件也以读的方式被打开;
#include<sys/stats.h>
int mkfifo(const char* pathname, mode_t mode);
//创建一个命名管道标识符
//pathname:带有路径管道标识符文件名
//mode:文件权限
//返回值有个特殊错误“EEXTST”,意思是文件已经存在,此时可以接着操作不需要退出
#include<sys/stat.h>
#include<sys/stats.h>
#include<fcntl.h>
#include<iostream>
int main(){
if((mkfifo("./test.file",0600) == -1) && errno != EEXIST){
perror("mkfifo failure\n");
return 0;
}
int fd=open("./test.file",O_RDONLY);
if(fd<0){
perror("open error");
return -1;
}
printf("open success\n");
while(1){
char buf[1024]={0};
int ret=read(fd,buf,1023);
if(ret<0){
perror("read error");
close(fd);
return -1;
}else if(ret==0){
perror("all write closed");
close(fd);
return -1;
}
cout<<buf<<endl;
}
close(fd);
}
//创建管道文件后对缓存区的读写操作与文件操作基本一致
共享内存
原理及本质:
申请了一块物理内存,需要进行数据共享的进程将该块空间映射至自己的虚拟空间,然后通过自己的虚拟地址直接访问该块物理内存;
特性:
①最快的进程间通信方式(覆盖式修改,因为直接通过虚拟地址访问物理内存少了数据拷贝操作);
②生命周期随内核,不是立即删除,映射链接数为0自动删除;
③自身不带同步互斥,多个进程对共享内存操作存在操作的安全隐患。
操作流程:
#include<sys/shm.h>
①创建或打开共享内存
int shmget(key_t key, size_t size, int shmflag);
//key:标识符;size:共享内存大小;shmflag:打开方式&权限
//成功则返回一个操作句柄(非负),失败则返回-1
②建立映射关系
void* shmat(int shmid, void* addr, int shmflag);
//shmid:shmget返回的操作句柄,表示要操作哪个共享内存
//addr:指定映射首地址,通常为NULL
//shmflag:SHM_RDONLY为只读,默认给0为可读可写
//成功则返回映射首地址,失败返回(void*)-1
③解除映射
int shmdt(void* shm_start);
//shm_start:shmat映射的首地址
④删除共享内存
int shmctl(int shmid,int cmd, struct shmid_ds* buff);
//shmid:shmget返回的操作句柄
//cmd:要对共享内存进行的操作 IPC_RMID
//buf:用于设置或者获取共享内存信息,不用则置空
消息队列
原理及本质:
本质是内核中的优先级队列,多个进程通过访问同一个内核中的消息队列,通过向队列中添加、获取结点实现数据传输;
(不常用)
特性:
①自带同步与互斥;
②生命周期随内核;
信号量
用于实现进程间的同步与互斥的技术;
原理及本质:
内核中的一个计数器&pcb等待队列,针对于资源进行计数,实现同步与互斥;
同步实现:
对于资源进行计数,在资源获取之前进行P操作,产生资源之后进行V操作;
互斥实现:
将计数器初始化为1,表示资源仅有一个,在访问数据之前进行P操作,访问完毕之后进行V操作。
操作:
P操作:获取或访问资源前进行P操作,对计数器进行判断,若小于等于0则阻塞进程,并且计数器-1,若计数器大于0,则计数-1,操作正确返回;
V操作:在产生资源之后进行V操作,计数+1,唤醒一个等待中的进程。