Linux中IPC机制:共享内存区

来源:互联网 发布:最好吃的方便面知乎 编辑:程序博客网 时间:2024/06/09 20:47

前言

研究IPMI源码,实现将IPMI获取的性能数据传递给另一独立进程,主要用到的IPC方法就是共享内存。现在来说,基本是对共享内存这一机制有了更加透彻的理解和掌握。记录下。

相关背景:

Linux中IPC有几种实现版本,例如共享内存就有Posix共享内存与System V共享内存,它们的联系与区别?
答:Linux中的进程间通信机制源自于Unix平台上的进程通信机制。Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者对Unix早期的IPC进行系统的改进和扩充,形成了运行在单个计算机上的System V IPC,后者则跳过单机限制,实现了基于socket的进程间通信机制。同时因为Unix版本的多样性,IEEE又单独制定了一套独立的Posix IPC标准。在这三者基础上,Linux所继承的IPC,如图示:

也就是说,Linux上的IPC版本之间只是实现不同,效果并没有区别。

共享内存区

关于共享内存机制具体的介绍我就不多说了,内容网上多的是,有书的话请详细阅读《网络编程,卷二》。这里,我先后使用过Mmap映射同一文件方式与System V API方式分别进行了实验,就其中遇到的一些问题说一说。


1,Mmap 映射

1)指定映射文件

适用于两个独立的任何进程,通过指定同一个文件来实现共享内存方式的进程间通信。需要注意的是,进程之间在共享内存时,共享内存会一直保持到通信完毕为止,数据内容会一直在内存中。在解除映射之后,内存上的内容才写回文件。

原理:所映射文件的实际数据成了被共享内存区的内容。那如何保证各个独立进程寻址到同一内存页面?

<1> page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的;

<2> 文件与address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面;

<3> 进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常;

<4> 对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区(swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表;

<5> 所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面;

以上原理出自参考的博文,对整个流程的理解,我用图表示:


实例仍然给出原博文例子,算是属于手抄版吧。。。反正是实例嘛,说明效果就好了。

/* example A */#include <sys/mman.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <string.h>typedef struct{  char name[4];  int  age;}people;void main(int argc, char** argv) // map a normal file as shared mem:{        int fd,i;        people *p_map;        char temp;        fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 00777); // 注意O_TRUNC选项参数        lseek(fd, sizeof(people)*5-1, SEEK_SET);        write(fd, "", 1);           p_map = (people*) mmap( NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );        close( fd );        temp = 'a';        for(i = 0; i < 10; i++)        {                temp += 1;                memcpy( (p_map+i)->name, &temp, 1 );                (p_map+i)->age = 20+i;        }        printf(" initialize over \n ");        sleep(10);        munmap( p_map, sizeof(people)*10 );        printf( "umap ok \n" );}
/* example B */#include <sys/mman.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>typedef struct{        char name[4];        int  age;}people;void main(int argc, char** argv)  // map a normal file as shared mem:{        int fd,i;        people *p_map;            fd=open( argv[1], O_CREAT|O_RDWR, 00777 );        p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE,                 MAP_SHARED, fd, 0);         for(i = 0; i < 10; i++)        {                   printf( "name: %s age %d;\n",(p_map+i)->name, (p_map+i)->age );        }        munmap( p_map, sizeof(people)*10 );}

这里需要注意的是open()文件时O_TRUNC的参数。这个每次打开文件时清零文件的,也就是说,如果有三个以上的进程需要访问此文件,打开时就会把之前两个进程的数据给清零掉,这是我们不愿意看到的。这个问题在一开始时没注意,出了问题还以为是共享内存本身机制的问题,后来逐一对照代码,才发现原因所在。刚学习的同学尤其注意,实例代码也不能拿来就用,还是要自己搞明白才好。

另外需要说明的是,我没有遇到原文中提到的进程B在未解除映射时和解除映射后对共享内存的访问时候的差异,实在没找的原因。对文件大小和映射内存大小的限制也没有体现。贴上实例效果,我在想是否有可能是现在的内核版本中已经完善,就是只要不跨越内存页,超过文件大小的共享内存区域依然能够访问



2)匿名映射文件

适用于具有亲缘关系的父子进程。原理同上。

所谓的匿名映射文件,只不过指定文件映射的一种特殊形式,本质上并无不同。这种形式主要就是基于Linux中父子进程创建的方式。父进程通过在调用fork()函数创建子进程之前,通过mmap()创建共享内存区,之后fork的子进程继承父进程资源,自然也就共享父进程mmap后的映射空间了。

直接贴上手抄版实例:

/* fork example */#include <sys/mman.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct{        char name[4];        int  age;}people;void main(int argc, char** argv){        int i;        people *p_map;        char temp;        p_map = (people *)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);         if (fork() == 0)        {                   sleep(2);                for (i = 0;i < 5;i++)                {                        printf("child read: the %d people's age is %d\n", i+1, (p_map+i)->age);                }                p_map->age = 100;                munmap(p_map, sizeof(people)*10); //实际上,进程终止时,会自动解除映射。                exit(1);        }        temp = 'a';        for (i = 0; i < 5;i++)        {                temp += 1;                memcpy((p_map+i)->name, &temp, 1);                (p_map+i)->age = 20+i;        }        sleep(5);        printf("parent read: the first people,s age is %d\n", p_map->age);        printf("umap\n");        munmap(p_map,sizeof(people)*10);        printf("umap ok\n");}

3) 对Mmap()返回地址的访问

这一块主要东西我觉得还是看《卷二》吧,其中的内容其实也还好,容易理解。

2,System V API

System V学习起来相对很简单,因为你只要会用接口就成。原理很mmap()文件映射是一样一样的。


总结

总的来说,共享内存对于数据的存取效率是很高效的。但目前为止,我觉得对于数据类型比较多,且结构复杂,像数组嵌套之类的用这个太蛋疼了。。因为我只知道一个共享内存的首地址,所以对内存的访问只能一个萝卜一个坑的去找。很不爽。不过,也可能是目前见识有限,暂就这些吧。

另外,真心觉得写这种技术型文章很费精力啊。。远不是贴两个程序,说两句话就成的。这方面,我做的不够。


主要参考:

博文:Linux环境进程间通信(五): 共享内存(上)

《网络编程,卷二》

0 0
原创粉丝点击