进程间通信方式(二)
共享映射区
(一).通信方式:
进程间通信又称IPC(Inter Process Communication),它可以通过文件 管道 有名管道 共享内存 消息队列 信号量 套接字这几个方式进行通信,但是文件这种以及消息队列基本已被淘汰。
所以常用的通信方式有:
1.管道
2.信号
3.共享映射区
4.本地套接字
(二).共享映射区基本概念:
通过mmap系统调用,把普通文件映射到存储空间的一个缓冲区中,使得两个或多个进程之间可以共享这一块区域,就达到了进程间通信的目的。这种通信方式比通过文件进行通信更快,因为是直接操作缓冲区,而不是通过read以及write等函数进行操作。
这种方式可以用于父子间进程通信,也可以用于无血缘关系的进程之间通信。
当我们使用mmap建立了共享映射区之后,进行的操作就好比我们操作字符数组一样。
(三).使用共享映射区进行通信:
函数原型:void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
所需头文件:#include <sys/mman.h>
参数含义:
1.第一个参数代表建立共享映射区在内存中的起始地址,由系统自动分配即可,所以传入NULL就行了;
2.第二个参数代表创建的映射区的大小;
3.第三个参数代表映射区的访问权限,由几个宏确定,常用的有:PROT_READ(可读),PROT_WRITE(可写),PROT_READ | PROT_WRITE(可读可写);
4.第四个参数代表标志位,常用的有:MAP_SHARED、MAP_PRIVATE、MAP_ANON,MAP_SHARED的意思是对这块映射区内容的修改会影响到被映射的文件 或者 多个进程间独享该映射区,MAP_PRIVATE的意思是对这块映射区内容的修改不会影响到被映射的文件 或者 多个进程间共享该映射区,MAP_ANON用于匿名映射;
5.第五个参数代表要映射的文件的文件描述符;
6.最后一个参数代表操作这块映射区的起始偏移量,和对数组的操作类似,不过必须是4k的整数倍。
返回值:
该函数成功调用会返回创建映射区的首地址,如果失败会返回一个宏:MAP_FAILED
由于mmap函数其实涉及到内存的分配,有了分配,自然要有释放。
释放的函数原型:int munmap(void *addr, size_t length)
头文件:#include <sys/mman.h>
参数含义:
1.第一个参数代表共享映射区的起始地址
2.第二个参数代表分配的大小
返回值:
成功返回0,失败返回-1
例子1:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
int main()
{
int len, ret;
char *p = NULL;
int fd = open("mytest.txt", O_CREAT | O_RDWR, 0666); //以读写的方式打开文件,不存在则创建
if(fd < 0)
{
perror("open error:");
exit(1);
}
len = ftruncate(fd, 4); //扩展文件大小
if(len == -1)
{
perror("ftruncate error:");
exit(1);
}
p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); //以读写的方式建立映射区
if(p == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
strcpy(p, "abc"); //将字符串abc赋给这块映射区
ret = munmap(p, 4); //释放
if(ret == -1)
{
perror("munmap error:");
exit(1);
}
close(fd);
return 0;
}
这个例子虽然简单,但是其实有几个需要注意的地方。
1.当我们打开一个文件的时候,操作该文件的权限是不能小于通过该文件建立映射区的权限的,即如果文件是只读的,那么映射区这边也不能进行写操作。
2.创建映射区时,包含着一次对映射文件的读操作,因为要把内容都映射到缓冲区中。所以文件的读权限应该要有才行。
3.偏移量必须是4k的整数倍。
4.munmap的第一个参数必须是映射区的首地址。
例子2:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
int main()
{
int *p;
pid_t pid;
//这次我们不打开文件,而是进行匿名映射。因为打开文件其实主要是起一个映射的作用,略显鸡肋,如果直接有这一块区域进行通信,那不更好
p = (int *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); //原本的文件描述符参数应该填-1
if(p == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
pid = fork();
if(pid == 0)
{
*p = 2;
printf("child, *p = %d\n", *p);
}
else
{
sleep(1);
printf("parent, *p = %d\n", *p);
wait(NULL);
int ret = munmap(p, 4);
if(ret == -1)
{
perror("mumap error");
exit(1);
}
}
return 0;
}
注意事项:
MAP_ANON参数只适合在linux系统中使用。
例子3:
/****************读数据******************/
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
struct STU
{
int id;
char name[20];
char sex;
};
int main(int argc, char *argv[]) //argv[1]用于传递要映射的文件名
{
int fd;
struct STU student;
struct STU *mm;
fd = open(argv[1], O_RDONLY);
mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0); //注意这里必须是MAP_SHARED,不然数据无法共享
if(mm == MAP_FAILED)
{
perror("mmap error:");
exit(1);
}
close(fd);
while(1) //读出映射区的数据
{
printf("id = %d\tname = %s\t %c\n", mm->id, mm->name, mm->sex);
sleep(2);
}
munmap(mm, sizeof(student));
return 0;
}
/***************写端***************/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
struct STU
{
int id;
char name[20];
char sex;
};
int main(int argc, char *argv[])
{
int fd;
struct STU student = {10, "xiaoming", 'm'};
char *mm;
fd = open(argv[1], O_RDWR | O_CREAT, 0664);
ftruncate(fd, sizeof(student));
mm = mmap(NULL, sizeof(student), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
close(fd);
while(1) //写数据
{
memcpy(mm, &student, sizeof(student));
student.id++;
sleep(1);
}
munmap(mm, sizeof(student));
return 0;
}
注意两个进程之间打开映射区的标志位必须是MAP_SHARED,而不是MAP_PRIVATE,否则数据无法共享。两个或多个无血缘关系的进程只要通过共享一块映射区,就可以达到通信的目的了。