UNIX网络编程第二卷进程间通信对mmap函数进行了说明。该函数主要用途有三个:
1、将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
2、将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
3、为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。
头文件:
#include <sys/mman.h>
函数:void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
参数start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
参数length:代表将文件中多大的部分映射到内存。
参数prot:映射区域的保护方式。可以为以下几种方式的组合:
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能存取
参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
返回值:
若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。
错误代码:
EBADF 参数fd 不是有效的文件描述词
EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
EINVAL 参数start、length 或offset有一个不合法。
EAGAIN 文件被锁住,或是有太多内存被锁住。
ENOMEM 内存不足。
系统调用mmap()用于共享内存的两种方式:
(1)使用普通文件提供的内存映射:
适用于任何进程之间。此时,需要打开或创建一个文件,然后再调用mmap()
典型调用代码如下:
fd=open(name, flag, mode); if(fd<0) ...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,可以参看UNIX网络编程第二卷。
(2)使用特殊文件提供匿名内存映射:
适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。
#
include
<
unistd.
h>
#
include
<
stdio.
h>
#
include
<
sys/
mman.
h>
#
include
<
fcntl.
h>
#
include
<
stdlib.
h>
//定义存放记录的结构体
typedef
struct
{
int
index;
//编号
char
text[
10]
;
//内容
}
RECORD;
#
define
SIZE (
50)
#
define
EDIT_INDEX (
10)
int
main(
void
)
{
RECORD record,
*
p_mapped_memory_addr;
int
i,
fd;
FILE
*
fp;
//创建文件并写入测试数据
fp =
fopen
(
"records.dat"
,
"w+"
)
;
for
(
i =
0;
i <
SIZE;
i+
+
)
{
record.
index =
i;
sprintf
(
record.
text,
"No.%d"
,
i)
;
fwrite
(
&
record,
sizeof
(
record)
,
1,
fp)
;
//因为字节序对齐,在32位机上,sizeof(record)=16,并不是14。
}
fclose
(
fp)
;
printf
(
"Ok, write %d records to the file: records.dat ./n"
,
SIZE)
;
//将第一30条记录编号修改为300,并相应地修改其内容。
//采用传统方式
fp =
fopen
(
"records.dat"
,
"r+"
)
;
fseek
(
fp,
EDIT_INDEX *
sizeof
(
record)
,
SEEK_SET
)
;
fread
(
&
record,
sizeof
(
record)
,
1,
fp)
;
record.
index =
EDIT_INDEX*
10;
sprintf
(
record.
text,
"No.%d"
,
record.
index)
;
fseek
(
fp,
EDIT_INDEX *
sizeof
(
record)
,
SEEK_SET
)
;
fwrite
(
&
record,
sizeof
(
record)
,
1,
fp)
;
fclose
(
fp)
;
printf
(
"Ok, edit the file of records.dat using traditional method./n"
)
;
/////////////////////////////////////////
//同样的修改,这次使用内存映射方式。
//将记录映射到内存中
fd =
open
(
"records.dat"
,
O_RDWR)
;
p_mapped_memory_addr =
(
RECORD *
)
mmap(
0,
SIZE *
sizeof
(
record)
,
PROT_READ |
PROT_WRITE,
MAP_SHARED,
fd,
0)
;
//修改数据
p_mapped_memory_addr[
EDIT_INDEX]
.
index =
EDIT_INDEX*
10;
sprintf
(
p_mapped_memory_addr[
EDIT_INDEX]
.
text,
"No.%d"
,
p_mapped_memory_addr[
EDIT_INDEX]
.
index)
;
/* Synchronize the region starting at ADDR and extending LEN bytes with the
file it maps. Filesystem operations on a file being mapped are
unpredictable before this is done. Flags are from the MS_* set.
This function is a cancellation point and therefore not marked with
__THROW. extern int msync (void *__addr, size_t __len, int __flags);
*/
//将修改写回映射文件中(采用异步写方式)
msync(
(
void
*
)
p_mapped_memory_addr,
SIZE *
sizeof
(
record)
,
MS_ASYNC)
;
/* Deallocate any mapping for the region starting at ADDR and extending LEN
bytes. Returns 0 if successful, -1 for errors (and sets errno).
extern int munmap (void *__addr, size_t __len) __THROW;
*/
//释放内存段
munmap(
(
void
*
)
p_mapped_memory_addr,
SIZE *
sizeof
(
record)
)
;
printf
(
"Ok, edit the file of records.dat using mmap method./n"
)
;
//关闭文件
close
(
fd)
;
return
0;
}
mmap
功能描述:
mmap将个文件或者其它对象映射进内存文件被映射到多个页上如果文件大小不是所有页大小的和最后个页不被使用空间将会清零munmap执行相反操作删除特定地址区域对象映射
基于文件映射在mmap和munmap执行过程任何时刻被映射文件st_atime可能被更新如果st_atime字段在前述情况下没有得到更新首次对映射区第个页索引时会更新该字段值用PROT_WRITE 和 MAP_SHARED标志建立起来文件映射其st_ctime 和 st_mtime
在对映射区写入的后但在msync通过MS_SYNC 和 MS_ASYNC两个标志的前会被更新
使用方法:
# <sys/mman.h>
void *mmap(void *start, size_t length, prot, flags,
fd, off_t off);
munmap(void *start, size_t length);
参数:
start:映射区开始地址
length:映射区长度
prot:期望内存保护标志不能和文件打开模式冲突是以下某个值可以通过or运算合理地组合在起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象类型映射选项和映射页是否可以共享它值可以是个或者多个以下位组合体
MAP_FIXED //使用指定映射起始地址如果由start和len参数指定内存区重叠于现存映射空间重叠部分将会被丢弃如果指定起始地址不可用操作将会失败并且起始地址必须落在页边界上
MAP_SHARED //和其它所有映射这个对象进程共享映射空间对共享区写入相当于输出到文件直到msync或者munmap被文件实际上不会被更新
MAP_PRIVATE //建立个写入时拷贝私有映射内存区域写入不会影响到原文件这个标志和以上标志是互斥只能使用其中个
MAP_DENYWRITE //这个标志被忽略
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要为这个映射保留交换空间当交换空间被保留对映射区修改可能会得到保证当交换空间不被保留同时内存不足对映射区修改会引起段违例信号
MAP_LOCKED //锁定映射区页面从而防止页面被交换出内存
MAP_GROWSDOWN //用于堆栈告诉内核VM系统映射区可以向下扩展
MAP_ANONYMOUS //匿名映射映射区不和任何文件关联
MAP_ANON //MAP_ANONYMOUS别称不再被使用
MAP_FILE //兼容标志被忽略
MAP_32BIT //将映射区放在进程地址空间低2GBMAP_FIXED指定时会被忽略当前这个标志只在x86-64平台上得到支持
MAP_POPULATE //为文件映射通过预读方式准备好页表随后对映射区访问不会被页违例阻塞
MAP_NONBLOCK //仅和MAP_POPULATE起使用时才有意义不执行预读只为已存在于内存中页面建立页表入口
fd:有效文件描述词如果MAP_ANONYMOUS被设定为了兼容问题其值应为-1
off:被映射对象内容起点
返回介绍说明:
成功执行时mmap返回被映射区指针munmap返回0失败时mmap返回MAP_FAILED[其值为(void *)-1]munmap返回-1errno被设为以下某个值
EACCES:访问出错
EAGAIN:文件已被锁定或者太多内存已被锁定
EBADF:fd不是有效文件描述词
EINVAL:个或者多个参数无效
ENFILE:已达到系统对打开文件限制
ENODEV:指定文件所在文件系统不支持内存映射
ENOMEM:内存不足或者进程已超出最大内存映射数量
EPERM:权能不足操作不允许
ETXTBSY:已写方式打开文件同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程内存区
共享内存可以说是最有用进程间通信方式也是最快IPC形式两个区别进程A、B共享内存意思是同块物理内存被映射到进程A、B各自进程地址空间进程A可以即时看到进程B对共享内存中数据更新反的亦然由于多个进程共享同块内存区域必然需要某种同步机制互斥锁和信号量都可以
采用共享内存通信个显而易见好处是效率高进程可以直接读写内存而不需要任何数据拷贝对于像管道和消息队列等通信方式则需要在内核和用户空间进行 4次数据拷贝而共享内存则只拷贝两次数据[1]:次从输入文件到共享内存区另次从共享内存区到输出文件实际上进程的间在共享内存时并不总是读写少量数据后就解除映射有新通信时再重新建立共享内存区域而是保持共享区域直到通信完毕为止这样数据内容直保存在共享内存中并没有写回文件共享内存中内容往往是在解除映射时才写回文件因此采用共享内存通信方式效率是非常高
Linux2.2.x 内核支持多种共享内存方式如mmap系统Posix共享内存以及系统V共享内存linux发行版本如Redhat 8.0支持mmap系统及系统V共享内存但还没实现Posix共享内存本文将主要介绍mmap系统及系统V共享内存API原理及应用
、内核怎样保证各个进程寻址到同个共享内存区域内存页面
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中进程最终将更新进程页表
注:对于映射普通文件情况(非共享映射)缺页异常处理首先会在page cache中根据address_space以及数据偏移量寻找相应页面如果没有找到则介绍说明文件数据还没有读入内存处理会从磁盘读入相应页面并返回相应地址同时进程页表也会更新
5、所有进程在映射同个共享内存区域时情况都样在建立线性地址和物理地址的间映射的后不论进程各自返回地址如何实际访问必然是同个共享内存区域对应物理页面
注:个共享内存区域可以看作是特殊文件系统shm中个文件shm安装点在交换区上
上面涉及到了些数据结构围绕数据结构理解问题会容易些
回页首
2、mmap及其相关系统
mmap系统使得进程的间通过映射同个普通文件实现共享内存普通文件被映射到进程地址空间后进程可以向访问普通内存样对文件进行访问不必再readwrite()等操作
注:实际上mmap系统并不是完全为了用于共享内存而设计它本身提供了区别于般对普通文件访问方式进程可以像读写内存样对普通文件操作而Posix或系统V共享内存IPC则纯粹用于共享目当然mmap实现共享内存也是其主要应用的
1、mmap系统形式如下:
void* mmap ( void * addr , size_t len , prot , flags , fd , off_t off )
参数fd为即将映射到进程空间文件描述字般由open返回同时fd可以指定为-1此时须指定flags参数中MAP_ANON表明进行是匿名映射(不涉及具体文件名避免了文件创建及打开很显然只能用于具有亲缘关系进程间通信)len是映射到进程地址空间字节数它从被映射文件开头off个字节开始算起prot 参数指定共享内存访问权限可取如下几个值或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED其中MAP_SHARED , MAP_PRIVATE必选其而MAP_FIXED则不推荐使用off参数般设为0表示从文件头开始映射参数addr指定文件应被映射到进程空间起始地址般被指定个空指针此时选择起始地址任务留给内核来完成返回值为最后文件映射到进程空间地址进程可直接操作起始地址为该值有效地址这里不再详细介绍mmap参数读者可参考mmap手册页获得进步信息
2、系统mmap用于共享内存两种方式:
(1)使用普通文件提供内存映射:适用于任何进程的间;此时需要打开或创建个文件然后再mmap;典型代码如下:
fd=open(name, flag, mode);
(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap实现共享内存通信方式有许多特点和要注意地方我们将在范例中进行具体介绍说明
(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系进程的间;由于父子进程特殊亲缘关系在父进程中先mmap然后fork那么在fork的后子进程继承父进程匿名映射后地址空间同样也继承mmap返回地址这样父子进程就可以通过映射区域进行通信了注意这里不是般继承关系般来说子进程单独维护从父进程继承下来些变量而mmap返回地址却由父子进程共同维护
对于具有亲缘关系进程实现共享内存最好方式应该是采用匿名内存映射方式此时不必指定具体文件只要设置相应标志即可参见范例2
3、系统munmap
munmap( void * addr, size_t len )
该在进程地址空间中解除个映射关系addr是mmap时返回地址len是映射区大小当映射关系解除后对原来映射地址访问将导致段发生
4、系统msync
msync ( void * addr , size_t len, flags)
般说来进程在映射空间对共享内容改变并不直接写回到磁盘文件中往往在munmap()后才执行该操作可以通过msync实现磁盘上文件内容和共享内存区内容致
回页首
3、mmap范例
下面将给出使用mmap两个范例:范例1给出两个进程通过映射普通文件实现共享内存通信;范例2给出父子进程通过匿名映射实现共享内存系统 mmap有许多有趣地方下面是通过mmap()映射普通文件实现进程间通信范例我们通过该范例来介绍说明mmap实现共享内存特点及注意事项
范例1:两个进程通过映射普通文件实现共享内存通信
范例1包含两个子:map_normalfile1.c及map_normalfile2.c编译两个可执行文件分别为 map_normalfile1及map_normalfile2两个通过命令行参数指定同个文件来实现共享内存方式进程间通信 map_normalfile2试图打开命令行参数指定个普通文件把该文件映射到进程地址空间并对映射后地址空间进行写操作 map_normalfile1把命令行参数指定文件映射到进程地址空间然后对映射后地址空间执行读操作这样两个进程通过命令行参数指定同个文件来实现共享内存方式进程间通信
下面是两个代码:
/*-------------map_normalfile1.c-----------*/
# <sys/mman.h>
# <sys/types.h>
# <fcntl.h>
# <unistd.h>
typedef struct{
char name[4];
age;
}people;
( argc, char** argv) // map a normal file as shared mem:
{
fd,i;
people *p_map;
char temp;
fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
lseek(fd,(people)*5-1,SEEK_SET);
write(fd,"",1);
p_map = (people*) mmap( NULL,(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,2 );
( *(p_map+i) ).age = 20+i;
}
prf(" initialize over /n ");
sleep(10);
munmap( p_map, (people)*10 );
prf( "umap ok /n" );
}
/*-------------map_normalfile2.c-----------*/
# <sys/mman.h>
# <sys/types.h>
# <fcntl.h>
# <unistd.h>
typedef struct{
char name[4];
age;
}people;
( argc, char** argv) // map a normal file as shared mem:
{
fd,i;
people *p_map;
fd=open( argv[1],O_CREAT|O_RDWR,00777 );
p_map = (people*)mmap(NULL,(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
for(i = 0;i<10;i)
{
prf( "name: %s age %d;/n",(*(p_map+i)).name, (*(p_map+i)).age );
}
munmap( p_map,(people)*10 );
}
map_normalfile1.c 首先定义了个people数据结构(在这里采用数据结构方式是共享内存区数据往往是有固定格式这由通信各个进程决定采用结构方式有普遍代表性)map_normfile1首先打开或创建个文件并把文件长度设置为5个people结构大小然后从mmap返回地址开始设置了10个people结构然后进程睡眠10秒钟等待其他进程映射同个文件最后解除映射
map_normfile2.c只是简单映射个文件并以people数据结构格式从mmap返回地址处读取10个people结构并输出读取值然后解除映射
分别把两个编译成可执行文件map_normalfile1和map_normalfile2后在个终端上先运行./map_normalfile2 /tmp/test_shm输出结果如下:
initialize over
umap ok
在map_normalfile1输出initialize over 的后输出umap ok的前在另个终端上运行map_normalfile2 /tmp/test_shm将会产生如下输出(为了节省空间输出结果为稍作整理后结果):
name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
name: g age 25; name: h age 26; name: I age 27; name: j age 28; name: k age 29;
在map_normalfile1 输出umap ok后运行map_normalfile2则输出如下结果:
name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
name: age 0; name: age 0; name: age 0; name: age 0; name: age 0;
从运行结果中可以得出结论
1、 最终被映射文件内容长度不会超过文件本身大小即映射不能改变文件大小;
2、可以用于进程通信有效地址空间大小大体上受限于被映射文件大小但不完全受限于文件大小打开文件被截短为5个people结构大小而在 map_normalfile1中化了10个people数据结构在恰当时候(map_normalfile1输出initialize over 的后输出umap ok的前)map_normalfile2会发现map_normalfile2将输出全部10个people结构值后面将给出详细讨论
注:在linux中内存保护是以页为基本单位即使被映射文件只有个字节大小内核也会为映射分配个页面大小内存当被映射文件小于个页面大小时进程可以对从mmap返回地址开始个页面大小进行访问而不会出错;但是如果对个页面以外地址空间进行访问则导致发生后面将进步描述因此可用于进程间通信有效地址空间大小不会超过文件大小及个页面大小和
3、文件旦被映射后mmap进程对返回地址访问是对某内存区域访问暂时脱离了磁盘上文件影响所有对mmap返回地址空间操作只在内存中有意义只有在了munmap后或者msync时才把内存中相应内容写回磁盘文件所写内容仍然不能超过文件大小
范例2:父子进程通过匿名映射实现共享内存
# <sys/mman.h>
# <sys/types.h>
# <fcntl.h>
# <unistd.h>
typedef struct{
char name[4];
age;
}people;
( argc, char** argv)
{
i;
people *p_map;
char temp;
p_map=(people*)mmap(NULL,(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
(fork 0)
{
sleep(2);
for(i = 0;i<5;i)
prf("child read: the %d people's age is %d/n",i+1,(*(p_map+i)).age);
(*p_map).age = 100;
munmap(p_map,(people)*10); //实际上进程终止时会自动解除映射
exit;
}
temp = 'a';
for(i = 0;i<5;i)
{
temp 1;
memcpy((*(p_map+i)).name, &temp,2);
(*(p_map+i)).age=20+i;
}
sleep(5);
prf( "parent read: the first people,s age is %d/n",(*p_map).age );
prf("umap/n");
munmap( p_map,(people)*10 );
prf( "umap ok/n" );
}
考察输出结果体会父子进程匿名共享内存:
child read: the 1 people's age is 20
child read: the 2 people's age is 21
child read: the 3 people's age is 22
child read: the 4 people's age is 23
child read: the 5 people's age is 24
parent read: the first people,s age is 100
umap
umap ok
回页首
4、对mmap返回地址访问
前面对范例运行结构讨论中已经提到linux采用是页式管理机制对于用mmap映射普通文件来说进程会在自己地址空间新增块空间空间大小由mmaplen参数指定注意进程并不定能够对全部新增空间都能进行有效访问进程能够访问有效地址大小取决于文件被映射部分大小简单说能够容纳文件被映射部分大小最少页面个数决定了进程从mmap返回地址开始能够有效访问地址空间大小超过这个空间大小内核会根据超过严重程度返回发送区别信号给进程可用如下图示介绍说明:
注意:文件被映射部分而不是整个文件决定了进程能够访问空间大小另外如果指定文件偏移部分定要注意为页面大小整数倍下面是对进程映射地址空间访问范例:
# <sys/mman.h>
# <sys/types.h>
# <fcntl.h>
# <unistd.h>
typedef struct{
char name[4];
age;
}people;
( argc, char** argv)
{
fd,i;
pagesize,off;
people *p_map;
pagesize = sysconf(_SC_PAGESIZE);
prf("pagesize is %d/n",pagesize);
fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
lseek(fd,pagesize*2-100,SEEK_SET);
write(fd,"",1);
off = 0; //此处off = 0编译成版本1;off = pagesize编译成版本2
p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,off);
close(fd);
for(i = 1; i<10; i)
{
(*(p_map+pagesize/(people)*i-2)).age = 100;
prf("access page %d over/n",i);
(*(p_map+pagesize/(people)*i-1)).age = 100;
prf("access page %d edge over, now begin to access page %d/n",i, i+1);
(*(p_map+pagesize/(people)*i)).age = 100;
prf("access page %d over/n",i+1);
}
munmap(p_map,(people)*10);
}
如中所注释那样把编译成两个版本两个版本主要体现在文件被映射部分大小区别文件大小介于个页面和两个页面的间(大小为:pagesize*2-99)版本1被映射部分是整个文件版本2文件被映射部分是文件大小减去个页面后剩余部分不到个页面大小(大小为:pagesize-99)中试图访问每个页面边界两个版本都试图在进程空间中映射pagesize*3字节数
版本1输出结果如下:
pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
access page 2 over
access page 2 over
access page 2 edge over, now begin to access page 3
Bus error //被映射文件在进程空间中覆盖了两个页面此时进程试图访问第 3个页面
版本2输出结果如下:
pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
Bus error //被映射文件在进程空间中覆盖了个页面此时进程试图访问第 2个页面
结论:采用系统mmap实现进程间通信是很方便在应用层上接口非常简洁内部实现机制区涉及到了linux存储管理以及文件系统等方面内容可以参考下相关重要数据结构来加深理解在本专题后面部分将介绍系统v共享内存实现