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 //将映射区放在进程地址空间低2GB
MAP_FIXED指定时会被忽略
当前这个标志只在x86-64平台上得到支持
MAP_POPULATE //为文件映射通过预读方式准备好页表
随后对映射区
访问不会被页违例阻塞
MAP_NONBLOCK //仅和MAP_POPULATE起使用时才有意义
不执行预读
只为已存在于内存中
页面建立页表入口
fd:有效文件描述词
如果MAP_ANONYMOUS被设定
为了兼容问题
其值应为-1
off:被映射对象内容
起点
返回介绍说明:
成功执行时mmap
返回被映射区
指针
munmap
返回0
失败时
mmap
返回MAP_FAILED[其值为(void *)-1]
munmap返回-1
errno被设为以下
某个值
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系统
使得进程的间通过映射同
个普通文件实现共享内存
普通文件被映射到进程地址空间后
进程可以向访问普通内存
样对文件进行访问
不必再
read
write()等操作
注:实际上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
映射普通文件来说
进程会在自己
地址空间新增
块空间
空间大小由mmap
len参数指定
注意
进程并不
定能够对全部新增空间都能进行有效访问
进程能够访问
有效地址大小取决于文件被映射部分
大小
简单
说
能够容纳文件被映射部分大小
最少页面个数决定了进程从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共享内存
实现