mmap函数用法及示例程序

UNIX网络编程第二卷进程间通信对mmap函数进行了说明。该函数主要用途有三个:
1、将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
2、将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
3、为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。


头文件:
   #include

函数: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两个标志 的前会被更新

  使用方法:

  #

  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]: 次从输入文件到共享内存区 另 次从共享内存区到输出文件 实际上 进程的间在共享内存时 并不总是读写少量数据后就解除映射 有新 通信时 再重新建立共享内存区域 而是保持共享区域 直到通信完毕为止 这样 数据内容 直保存在共享内存中 并没有写回文件 共享内存中 内容往往是在解除映射时才写回文件 因此 采用共享内存 通信方式效率是非常高

Linux 2.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-----------*/
#
#
#
#
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;
}
pr f(" initialize over /n ");
sleep(10);

munmap( p_map, (people)*10 );
pr f( "umap ok /n" );
}

/*-------------map_normalfile2.c-----------*/
#
#
#
#
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 )
{
pr f( "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:父子进程通过匿名映射实现共享内存


#
#
#
#
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 )
pr f("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);
pr f( "parent read: the first people,s age is %d/n",(*p_map).age );
pr f("umap/n");
munmap( p_map, (people)*10 );
pr f( "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 返回 地址开始 能够有效访问 地址空间大小 超过这个空间大小 内核会根据超过 严重程度返回发送区别 信号给进程 可用如下图示介绍说明:


注意:文件被映射部分而不是整个文件决定了进程能够访问 空间大小 另外 如果指定文件 偏移部分 定要注意为页面大小 整数倍 下面是对进程映射地址空间 访问范例:


#
#
#
#
typedef struct{
char name[4];
age;
}people;

( argc, char** argv)
{
fd,i;
pagesize,off ;
people *p_map;

pagesize = sysconf(_SC_PAGESIZE);
pr f("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;
pr f("access page %d over/n",i);
(*(p_map+pagesize/ (people)*i-1)).age = 100;
pr f("access page %d edge over, now begin to access page %d/n",i, i+1);
(*(p_map+pagesize/ (people)*i)).age = 100;
pr f("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共享内存 实现

你可能感兴趣的:(mmap函数用法及示例程序)