mmap系统调用它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,mmap()实现共享内存也是其主要应用之一。mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
我们的程序中大量运用了mmap,用到的正是mmap的这种“像访问普通内存一样对文件进行访问”的功能。实践证明,当要对一个文件频繁的进行访问,并且指针来回移动时,调用mmap比用常规的方法快很多。简单说就是把一个文件的内容在内存里面做一个映像,内存比磁盘快些。基本上它是把一个文件对应到virtual memory 中的一段,并传回一个指针。以后对这段内存做存取时,其实就是对那个档做存取。它就是一种快速文件I/O,而且使用上和存取内存一样方便,只不过会占掉你的 virutal memory。
mmap这个系统调用可以直接对底层的操作,映射硬件地址,实现用户层驱动。
#include
void*mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
intmunmap(void *addr, size_t len);
该函数各参数的作用图示如下
如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。len参数是需要映射的那一部分文件的长度。off参数是从文件的什么位置开始映射,必须是页大小的整数倍(在32位体系统结构上通常是4K)。filedes是代表该文件的描述符。
prot参数有四种取值:
PROT_EXEC表示映射的这一段可执行,例如映射共享库
PROT_READ表示映射的这一段可读
PROT_WRITE表示映射的这一段可写
PROT_NONE表示映射的这一段不可访问
flag参数有很多种取值,这里只讲两种,其它取值可查看mmap(2)
MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。
如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。
系统调用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 traditionalmethod./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 withthe
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 extendingLEN
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 mmapmethod./n" ) ;
//关闭文件
close ( fd) ;
return 0;
}