进程间通信是指在不同进程之间传播或交换信息,在Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间,进程之间不能相互访问。必须通过内核才能进行数据交换。如图:
常见的通信方式有以下几种:
接下来我们将详细介绍共享存储
在讲解内存映射之前,我们先简单了解一些虚拟内存的概念。
虚拟内存为每个进程提供了一个大的、一致的和私有的地址空间,它提供了3个能力:
VM系统将虚拟内存分割为虚拟页,物理内存也被分隔为物理页。
使用寄存器中的内存管理单元[MMU(Memory Management Unit)],利用存放在主存中的页表来动态翻译虚拟地址,就可以使用虚拟地址去访问相应的物理地址。
内存映射
内存映射,通过将虚拟内存区域与磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容。
磁盘上的对象可以是两类,Linux文件系统中的普通文件,或是匿名文件(由内核创建)。
mmap
函数Linux进程可以使用mmap
函数来创建新的虚拟内存区域,并将对象映射到这些区域中。
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
//成功:返回创建的映射区首地址;失败:MAP_FAILED宏
参数
PROT_READ
、PROT_WRITE
、PROT_READ|PROT_WRITE
MAP_SHARED
: 会将映射区所做的操作反映到物理设备(磁盘)上。MAP_PRIVATE
: 映射区所做的修改不会反映到物理设备。mmap建立的映射区在使用结束后也应调用类似free的函数来释放。
int munmap(void *addr, size_t length);
//成功:返回0; 失败:返回-1
mmap
进程间通信mmap
建立的映射区来完成数据通信。但是相应的要在创建映射区的时候指定对应的标志位参参数flags。MAP_PRIVATE
(私有映射)父子进程各自独占映射区MAP_SHARED
(共享映射)父子进程共享映射区int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
mmap
指定fd,将虚拟内存地映射到同一个物理内存地址上。代码样例 父子进程间通信
#include
#include
#include
#include
#include
#include
int var = 100;
int main(void)
{
int *p;
pid_t pid;
int fd;
fd = open("temp", O_RDWR|O_CREAT|O_TRUNC, 0644);
if(fd < 0){
perror("open error");
exit(1);
}
unlink("temp"); //删除临时文件目录项,使之具备被释放条件.
//将参数fd指定的文件大小改为参数length指定的大小
ftruncate(fd, 4);
//MAP_SHARED
//父子进程各自独占映射区
p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
//MAP_PRIVATE
//父子进程共享映射区
//p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(p == MAP_FAILED){ //注意:不是p == NULL
perror("mmap error");
exit(1);
}
close(fd); //映射区建立完毕,即可关闭文件
pid = fork(); //创建子进程
if(pid == 0){
*p = 2000;
//非共享变量
//在子进程中改为1000,父进程中仍为100
var = 1000;
printf("child, *p = %d, var = %d\n", *p, var);
} else {
sleep(1);
printf("parent, *p = %d, var = %d\n", *p, var);
wait(NULL);
int ret = munmap(p, 4); //释放映射区
if (ret == -1) {
perror("munmap error");
exit(1);
}
}
return 0;
}
使用文件也可以完成进程间通信,父进程使用fork
创建子进程后,父子进程共享文件描述符,也就是说,共享打开的文件。
/*
*父子进程共享打开的文件描述符------使用文件完成进程间通信.
*/
#include
#include
#include
#include
#include
#include
int main(void)
{
int fd1, fd2; pid_t pid;
char buf[1024];
char *str = "---------test for shared fd in parent child process-----\n";
pid = fork();
if (pid < 0) {
perror("fork error");
exit(1);
//子进程进入
} else if (pid == 0) {
fd1 = open("test.txt", O_RDWR);
if (fd1 < 0) {
perror("open error");
exit(1);
}
write(fd1, str, strlen(str));
printf("child wrote over...\n");
//父进程进入
} else {
fd2 = open("test.txt", O_RDWR);
if (fd2 < 0) {
perror("open error");
exit(1);
}
sleep(1); //保证子进程写入数据
int len = read(fd2, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
wait(NULL);
}
return 0;
}
看到这里你可能会有一些疑惑,文件进程间通信和有名管道FIFO有什么区别呢?
我们知道管道只有两端,读端和写端,有名管道可以控制读写两端,保证他们的同步性。
但是对于文件进程间进程通信而言,我们必须自己定义一些同步机制来控制读数据和写数据,否则可能会造成一些错误。
共享存储是非常重要的一种进程间通信方式,理解起来也相对来说比较困难有点,但是在了解了一些基本概念之后再去学习就会容易得多。