(操作系统为用户提供的几种进程间通信方式,让进程之间能够进行通信)
进程之间无法直接通信:
一个进程在访问一个数据的时候都是通过地址来进行访问,然而进程中的地址都是虚拟地址,经过页表映射之后访问物理内存,传递虚拟地址给另一进程,经过其页表映射访问不了原先数据(自己页表中没有对方数据的映射信息),因为得到的物理地址不对。进程间是被隔离开的。
进程之间应该具有独立性,独立意味着稳定
操作系统提供进程间通信方式,就是给多个需要通信的进程之间建立起一个关联:能够共同访问的一块内存
场景不同,有多种方式:管道、共享内存、消息对列、信号量
管道
管道:艺术来源于生活
作用:用于实现资源(数据)的传输
特性:半双工–可以选择方向的单向通信(同一时间不能同时即接受又发送)
本质:内核中的一块缓冲区(内核空间中的内存–有系统进行管理)。linux下一切皆文件,因此管道也当做文件来处理,但不是磁盘文件,是内存。
分类:匿名管道:没有标识符,不能被其他进程找到,只有具有亲缘关系的进程间通信(家族财产),子进程复制父进程,会复制同一个管道的操作句柄,因此能访问。
命名管道:有表示符,能被其他进城找到,同一主机上的任意进程间通信
创建管道后,将标准输入输出描述符,重定向到管道;这时候操作标准输入输出,就是操作管道。
匿名管道
操作:
int pipe(int pipefd[2]);
功能:创建一个管道,并通过参数返回管道的两个操作句柄,句柄–文件描述符
参数:piped–具有两个整型元素数组,内部创建管道将文件描述符存储在数组中
piped【0】–从管道读 ; piped【1】–向管道写
成功返回0,失败返回-1
创建匿名管道,一定要在创建子进程之前
/*
定义一个数组int pipefd[2];调用pipe()函数,将创建的数组指针传入,则会返回(通过指针的返回)给
pipefd数组两个操作匿名管道的操作句柄(读写),管道同时只能一端读,一端写,半双工通信,创建子进程,
因为匿名管道只能在家族中使用,并且是子进程fork复制父进程资源才获取到的,所以要在创建子进程之前,
创建pipe,本身只是父进程的资源,通过fork子进程也获取到,
fork()返回值,对进程返回0(创建成功),对父进程返回子进程pid
wait(NULL)等待任意一个子进程退出
*/
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
int main()
{
int pipefd[2];
//pipe[0]--读 pipe[1] ---写
int ret=pipe(pipefd);
if(ret<0)
{
perror("pipe error");
return -1;
}
int pid1=fork();
if(pid1==0){
//sleep(3);
char* data="我是大哥,听我的\r\n";
//ssize_t write(int fd, char* data, int len);
int sum=0;
while(1){
sleep(1);
int num= write(pipefd[1],data,strlen(data));
sum+=num;
printf("已经写入了%d字节的数据\n",sum);
}
exit(0);
}
int pid2=fork();
if(pid2==0){
printf("老二已经醒了,可以读数据了\n");//要有'\n',刷新缓冲区
while(1){
sleep(1);
char buf[1024]={0};
read(pipefd[0],buf,1023);
printf("弟弟收到通知:%s\n",buf);
}
sleep(2000);
exit(0);
}
wait(NULL);//等待任意一个子进程退出
wait(NULL);//等待另一个子进程退出
return 0;
}
管道特性:
1.管道中没有数据,read从管道取数据会阻塞,直到有数据了,读取到数据后才返回
2.如果管道中数据满了,则write会被阻塞,直到管道中有剩余空间(取出一些数据)
3.管道的所有读端被关闭,则继续向管道写入数据会导致进程崩溃退出(没有进程再去读取数据,所以这时写入也就没有意义了,系统就给进程干掉了)
4.管道的所有写段被关闭,则read从管道读取完所有数据后,将不在阻塞,而是返回0;(所有的写端被关闭,不会再有新的数据进入管道,读完管道中剩余的就没必要,再继续等了,可以通过read的返回值0,来决定什么时候停止从管道读)
模拟实现:ps -ef | grep pipe1
命名管道
命名管道:
内核中的一块缓冲区,有名字,能够被其他进程找到
通信原理:一个进程创建了一个命名管道的名字多个进程通过相同的管道名字,打开同一管道,访问同一块内存
名字:是一个可见于文件系统的管道文件(命名管道文件,虽然是个文件,但是实际上只是一个名字,能够让多个进程通过打开同一命名管道文件,进而获取到同一管道缓冲区,描述信息或者说操作句柄,进而访问同一块缓冲区进行通信)
实质上仍是通过内核缓冲区完成通信,而不是这个文件
操作:mkfifo命令,mkfifo函数
查看mkfifo函数: man 3 mkfifo
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
//创建管道并打开管道,进行写入
int main()
{
umask(0);//将当前进程文件创建掩码设置为0
int ret=mkfifo("./test.fifo",0664);
if(ret < 0 && errno != EEXIST)//容错处理,errno是一个全局变量,每个系统调用接口返回之前都会重置自己的错误编号
{
perror("mkfifo error");
return -1;
}
int fd=open("./test.fifo",O_WRONLY);//管道文件这里打开,不要使用O_CREAT
if(fd < 0){
perror("open error");
return -1;
}
while(1)
{
printf("小米");
fflush(stdout);
char buf[1024]={0};
scanf("%s",buf);
int ret=write(fd,buf,strlen(buf));
if(ret<0){
perror("write error");
close(fd);
return -1;
}
}
return 0;
}
读管道
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
//读取管道数据
int main()
{
umask(0);//将当前进程文件创建掩码设置为0
int ret=mkfifo("./test.fifo",0664);
if(ret < 0 && errno != EEXIST)//容错处理,errno是一个全局变量,每个系统调用接口返回之前都会重置自己的错误编号
{
perror("mkfifo error");
return -1;
}
int fd=open("./test.fifo",O_RDONLY);//管道文件这里打开,不要使用O_CREAT
if(fd < 0){
perror("open error");
return -1;
}
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)
{
printf("所有写端被关闭!\n");
close(fd);
return -1;
}
printf("%s\n",buf);
}
close(fd);
return 0;
}
所有读端被关闭,继续写就会异常;所有写端被关闭,继续读,读取完数据后就不在阻塞,而是返回0
可以这样测试,先调用写操作,再调用读操作
调试:gdb ./main ; 逐步调试: start ; 下一步: n ; 调试的文件编译 gcc -g main.c -o main
管道是按字节流传输的的半双工通信方式,自带同步(按照合理的顺序推进)与互斥(同一时间唯一访问,保证安全)
共享内存
共享内存:
用于多个进程间的数据共享,最快的进程间通信方式
原理:开辟出一块物理内存,多个进程将这块内存映射到自己的虚拟地址空间,通过虚拟地址直接访问物理地址(共享内存)
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
与管道相比:共享内存少了两次数据拷贝,因为通过管道传递数据首先要将数据写入管道,要获取数据还要从管道中读,共享内存相当于是自己拥有内存,直接访问即可,IO访问效率和内存访问效率,相差很大,所以这也是速度快的原因。
接口:
ftok函数
key_t ftok(const char* pathname, int proj_id);
根据文件的inode节点号,与一个proj_id组合一个key出来,ftok只是利用参数,再运用一套算法,算出一个唯一的key值返回。这个key值可以传给共享内存参数,作为struct ipc_perm中唯一标识共享内存的key。
获取共享内存:
key: 共享内存标识符(name) ; size : 内存大小,最好设置为PAGE_SIZE的整数倍 ;
shmflg:IPC_CREAT(不存在会创建打开,存在直接打开) ; IPC_EXCL:与IPC_CREAT搭配使用,共享内存不存在会创建打开,存在会报错返回 ; mode_flags:权限 0664;
返回值:成功返回操作句柄(size_t) ; 失败返回-1;
创建映射,返回首地址:
shmid: shmget打开共享内存时,返回的操作句柄
addr:映射首地址,通常置NULL,让操作系统进行分配
shmflag:默认0–可读可写,SHM_RDONLY表示只读(必须具备对共享内存的操作权限)
返回值:成功返回映射首地址,失败返回(void*)-1;
获取到首地址后,就可以通过首地址访问内存中的数据,以及可以修改内存中的数据
解除映射:int shmdt(const void* shmaddr);参数:映射首地址
cmd:要进行的操作 ; IPC_RMID:标记一个共享内存段需要被删除 ;
buf:设置或者获取共享内存信息,当cmd是IPC_RMID时被忽略
读操作:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#define SHM_KEY 0x12345678
#define PROJ_ID 0x11111111
int main()
{
//创建打开共享内存shmget(key, size, flag)
//key_t ftok(const char* pathname, int proj_id);根据文件的inode节点号,与一个proj_id组合一个key出来
//key_t key = ftok("./",PROJ_ID)
int shmid=shmget(SHM_KEY,4096,IPC_CREAT|0664);
if(shmid < 0){
perror("shmget error");
return -1;
}
//void* shmat(shmid, addr, flag)
void* start = shmat(shmid, NULL, 0);//返回内存首地址
if(start == (void*)-1){
perror("shmat error");
return -1;
}
while(1){
printf("%s\n",start);//对共享内存操作,操作可以有很多,这里只是打印
sleep(1);
}
shmdt(start);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
写操作:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#define SHM_KEY 0x12345678
#define PROJ_ID 0x11111111
int main()
{
//创建打开共享内存shmget(key, size, flag)
//key_t ftok(const char* pathname, int proj_id);根据文件的inode节点号,与一个proj_id组合一个key出来
//key_t key = ftok("./",PROJ_ID)
int shmid=shmget(SHM_KEY,4096,IPC_CREAT|0664);
if(shmid < 0){
perror("shmget error");
return -1;
}
//void* shmat(shmid, addr, flag)
void* start = shmat(shmid, NULL, 0);//返回内存首地址
if(start == (void*)-1){
perror("shmat error");
return -1;
}
int id=0;
int n=10;
while(n--){
sprintf(start, "马上就要 下雪了!!+%d\n",id++);//格式化数据放入start指向的内存中
sleep(1);
}
shmdt(start);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}