目录
命名管道
上期我们学习了匿名管道,匿名管道本质就是一个文件,且匿名管道适用于有血缘关系的进程进行通信,那如果有两个没有血缘关系的进程,那么这两个进程该如何进行通信呢?这就需要我们今天学习的命名管道,命名管道适用于没有血缘关系的两个进程进行通信。
我们知道,两个进程要进行通信的前提就是两个进程看到同一份资源,所以对于两个没有血缘关系的进程,通信的前提就是让这两个进行看到同一份资源。所以我们就需要创建命名管道,命名管道本质上也是文件。
命名管道的创建
命名管道有两种创建方法。
- 在命令行输入指令进行创建:mkfifo 管道名称
- 在程序中进行创建:int mkfifo(const char* filename,mode_t mode),第一个参数为创建的管道的路径,第二个为管道的访问权限,因为命名管道也是一个文件。
匿名管道和命名管道的区别
- 匿名管道使用pipe函数进行创建并打开
- 命名管道使用mkfifo函数进行创建,使用open函数打开。
- 因为命名管道和匿名管道本质都是文件,所以打开之后,后续的进程的读写操作都是类似的。
在这里声明一下,按照正常的操作流程,文件的读写如上图所示,写文件时,进程1先调用系统接口wirte()将要写的数据舒心到内核缓冲区,然后调用驱动中的接口write()将数据最终刷新到文件中,写完毕。进程2读文件时操作系统先把底层文件中的数据通过驱动中的read()接口读取到文件内核缓冲区,然后进程2调用read()接口从文件缓冲区中读取到数据,读完毕。
上面的操作步骤是一个正确完整的步骤,但是对于进程间通信而言,使用匿名管道和命名管道进行通信时,并不像上述的操作步骤一样进行读写,而是写时不去调用驱动中的接口刷新到底层文件,读时不从底层文件读取到文件内核缓冲区,省去了底层文件的交互,转化为从文件内核缓冲区中进行交互,这样可以大大的提升效率。
命名管道的代码实现
sever端口进行命名管道的创建以及数据的读取。代码如下
#include"comm.h"
int main()
{
//创建匿名管道
if(mkfifo(PATH,0666)<0)
{
perror("mkfifo");
return 1;
}
//创建管道成功,打开管道
int fd=open(PATH,O_RDONLY);
if(fd<0)
{
perror("open");
return 2;
}
//进行读取操作
char buff[64]={0};
while(1)
{
int num=read(fd,buff,sizeof(buff)-1);
if(num>0)
{
printf("%s\n",buff);
}
else if(num==0)
{
perror("client quit");
break;
}
else{
perror("read");
break;
}
}
close(fd);
return 0;
}
client端口进行命名管道中数据的写入。
#include"comm.h"
#include<string.h>
int main()
{
//sever端已经创建了命名管道,client只需要打开即可
//打开文件
int fd=open(PATH,O_WRONLY);
if(fd<0)
{
perror("open");
return 1;
}
//client端进行写操作
const char* msg="hello yjd";
while(1)
{
write(fd,msg,strlen(msg));
}
close(fd);
return 0;
}
comm.h中存放client和sever端需要的头文件以及生成的命名管道路径。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define PATH "./fifo"
运行结果如下。
我们发现sever端口确实读取到了client端口写入的数据。
共享内存
共享内存是基于system v标准下的一个IPC资源。无论是匿名管道还是命名管道,本质上都是基于文件的共享资源。而共享内存则是基于内存的,相当于在内存中开辟了一块空间作为两个进程的公共资源。示意图如下。
总体来说,操作系统在内存中申请一块空间作为共享内存,然后通过页表映射到进程的虚拟地址空间中,此时站在进程的角度就可以通过使用虚拟地址空间中的内存达到了访问共享内存的目的。
但是与此同时也产生了几个问题。站在用户的角度,怎么创建共享内存?两个进程怎样和共享内存关联起来,又怎样去除关联?怎样删除共享内存? 下来我们一一解答。这个四个问题刚好也是操作共享内存的整个流程。
创建共享内存
形参含义:key为在操作系统层面识别唯一共享内存的一个值。size为创建的共享内存的大小一般为一页4KB,shmflg为一个标记值,当shmflg为IPC_CREATE时,为创建一个共享内存,如果当前-+key值对应的共享内存不存在则创建,存在则返回存在的共享内存。当shmflg为IPC_CREATE|IPC_EXCL时,为如果key值所对应的共享内存不存在则创建,存在在返回错误码。
返回值:如果创建成功则返回一个id,这个id为一个标识码,可用于用户识别唯一共享内存。 失败返回-1。
其中的key值我们一般使用ftok函数进行获取。
形参含义:pathname为我们随意指定的一个路径,proj_id为我们随意制定的一个整型。
返回值:成功则返回一个key值,用于操作系统标识唯一共享内存。失败则返回-1。
关联共享内存
形参含义: shmid为创建共享内存成功时返回的值,即共享内存对于用户的唯一标识。剩下两个参数不用了解,无脑NULL和0即可。
返回值:若进程关联共享内存成功,返回值为共享内存映射到进程虚拟地址空间中的首地址,失败则返回-1。
去关联共享内存
形参含义:shmaddr为关联共享内存函数shmat的返回值。
返回值:成功返回0,失败返回-1。
删除共享内存
形参含义:shmid为创建共享内存时的返回值。cmd设置为IPC_RMID即可,最后一个buf默认设置为空即可。
返回值:成功则返回0,失败返回-1。
共享内存特点
共享内存的生命周期是随着操作系统的内核的。而之前的命名和匿名管道都是文件,文件都是由进程打开的,所以命名和匿名管道的周期都是随进程的。
共享内存中数据的读写不需要任何缓冲区,写端一写,读端就立即可以读。所以共享内存是进程间通讯速度最快的一种通信方式。
共享内存代码实现
sever端口创建共享内存,并进行数据的读取。
#include"comm.h"
#include<unistd.h>
int main()
{
//获取key
key_t key=ftok(PATH,NUM);
if(key<0)
{
perror("ftok");
return 1;
}
printf("ftok success\n");
//创建共享内存
int id = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
if(id<0)
{
perror("shmget");
return 2;
}
printf("shmget success\n");
//创建成功,进程1绑定共享内存
char* mem=(char*)shmat(id,NULL,0);
printf("shmat success\n");
//逻辑操作
while(1)
{
sleep(3);
printf("%s\n",mem);
}
//去关联共享内存
shmdt(mem);
//删除共享内存
shmctl(id,IPC_RMID,NULL);
return 0;
}
client端进行数据的写入。
#include"comm.h"
#include<unistd.h>
int main()
{
//得到一个key值,这个key必须与sever端的key值一样,才能保证得到相同的共享内存
key_t key=ftok(PATH,NUM);
if(key<0)
{
perror("ftok");
return 1;
}
//得到一个与sever端相同的共享内存
int id=shmget(key,SIZE,IPC_CREAT);
if(id<0)
{
perror("shmget");
return 2;
}
//进程2绑定共享内存
char* mem=(char*)shmat(id,NULL,0);
printf("shmat success\n");
//处理业务逻辑
char ch='A';
while(ch <= 'Z')
{
mem[ch-'A'] = ch;
ch++;
mem[ch-'A'] = 0;
sleep(2);
}
//去关联共享内存
shmdt(mem);
//共享内存的删除进程2不用参与,因为进程1创建并删除共享内存
return 0;
}
comm.h中存放client端和sever端需要的头文件。
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATH "./"
#define NUM 0x6666
#define SIZE 4096
运行结果如下。
运行结果符合预期。
以上便是共享内存的所有内容。
IPC资源总结
共享内存,消息队列和信号量,其实他们都是system V标准下的IPC资源,只要是IPC资源,那么操作这些资源的接口以及描述这些资源的数据结构就都一定是类似的。
在内存中,操作系统创建共享内存可能有多个,操作系统为了讲这些共享内存管理起来,按照先描述,后组织的六子真言,操作系统创建了shmid_ds类型的结构体用来描述共享内存,这个结构体的第一个成员变量struct ipc_perm也是一个结构体,第一个成员变量就是操作系统表示唯一共享内存的一个值。
消息队列的结构体如上图,我们发现与共享内存相似。
信号量的结构体如上图,与共享内存也相似,所以这也验证了IPC资源的接口与结构体类似的结论。至于为什么类似,是因为底层有一个指针数组,是struct ipc_perm* arr[N]类型的指针数组,首先把每个IPC资源的结构体对象的地址强转为struct ipc_perm*类型赋值给指针数组的元素,最终访问IPC资源所对应的结构体时,指针数组的元素会被强转为对应结构体类型的指针变量去访问对应类型的结构体的成员变量。
以上便是进程间通信的所有知识点。
本期内容到此结束^_^