首先说明一下mmap函数用途:

采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需
要任何数据的拷贝
1、将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读
   写取代I/O读写,以获得较高的性能;
2、将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,在父进程中先调用mmap
(),然后调用 fork()。那么在调用fork()
之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()
返回的地址,这样,父子进程就可以通过映射区域进行通信了。
3、为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。


普通文件fd用于mmap参数:
1、最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小
2、内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分
   配一个页面大小的内存,用于进程间通信的有效地址空间大小不会超过文件大小及一个页
   面大小的和
3、文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时
   脱离了磁盘上文件的影响,只有在调用了munmap()后或者msync()时,才把内存中的相应
   内容写回磁盘文件,所写内容仍然不能超过文件的大小。


A、ftok使用说明
key_t ftok( char * fname, int id )  fname就时你指定的文件名,id是子序号。


在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换
算成16进制为0x26,则最后的key_t返回值为0x26010002。


当删除重建文件后,索引节点号由操作系统根据当时文件系统的使用情况分配,因此与原来
不同,所以得到的索引节点号也不同。


如果要确保key_t值不变,要目确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值


注意:
ftok将一个已存在的路径名和一个整数标识符转换成一个key_t值。 


ftok会组合三个值来产生key:
1、pathname所在的文件系统的信息。
2、该文件在本文件系统内的索引节点号。
3、id的低序8位。 
 
key_t
的生成是以一个已存在的文件作为输入,并不是简单的字符串散列函数,必须真正存在某
个文件,才能将其位置传入ftok。


B、文件空洞是怎么回事
off_t lseek(int filedes, off_t offset, int whence);
参数 offset 的含义取决于参数 whence:
当前文件偏移量(current file offset),以下简称为 cfo,非负整数
    1. 如果 whence 是 SEEK_SET,文件偏移量将被设置为 offset。
    2. 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,
       offset 可以为正也可以为负。
    3. 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,
       offset 可以为正也可以为负。


注意: 对于普通文件(regular file),cfo 是一个非负整数。但对于特殊设备,cfo 
有可能是负数。因此,我们不能简单地测试 lseek 的返回值是否小于 0 来判断 lseek 
成功与否,而应该测试 lseek 的返回值是否等于 -1 来判断 lseek 成功与否。


if (lseek(fd, 16384, SEEK_SET) == -1)
{
    printf("lseek error\n");
    return -1;
}


测试结果表明,lseek并不能extend文件大小,需要write一下0的数据
/**
  * 参看前面man手册中的说明,mmap()不能用于扩展文件长度。所以这里必须事
  * 先扩大目标文件长度,准备一个空架子等待后面读写使用:
  * 如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。
  * 这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节
  * 由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的
  */ 
int tmpData = 0x00;
lseek(fd,maxSize-4,SEEK_SET);
write(fd,(const void*)&tmpData,sizeof(ui32)); /* 写入一个int型数据长度 */
或者
write(fd, "\0", 1); /* 在文件最后添加一个空字符 */

也可以使用ftruncate(改变文件大小)进行设定大小。


即对于普通文件的映射前,首称必须将fd的文件扩展到与你所映射空间的同样大小,这
样子后面映射后才可使用,否过会越界使用而出现死机


high memory |-------| A
            |       |
            |       |
            |       | memory mapped partion of file 
            |       |
            |       |
low memory  |-------| B


file mapped |-----------------|
            |offfset|   len   |
            |-----------------|
                    A'       B'
以上fd[A--B]映射到[A'--B']范围内,操作[A-B]与操作下面file mapped一样                    


C、copy_to_user与mmap的工作原理
copy_to_user
在每次拷贝时需要检测指针的合法性,也就是用户空间的指针所指向的地址的确是一段
该进程本身的地址,而不是指向了不属于它的地方,而且每次都会拷贝一次数据,频繁
访问内存,由于虚拟地址连续,物理地址不一定会连续,从而造成CPU的CACHE频繁失效,
从而使速度降低


mmap优点:
仅在第一次使用时为进程建立页表,也就是将一段物理地址映射到一段虚拟地址上,以后
操作时不再检测其地址的合法性(合法性交由CPU页保护异常来做),另一方面是内核下
直接操作mmap地址,可以不用频繁拷贝,也就是说在内核下直接可用指针向该地址操作,
而不再在内核中专门开一个缓冲区,然后将缓冲区中的数据拷贝一次进来,mmap一般是
将一段连续的物理地址映射成一段虚拟地址,当然,也可以将每段连续,但各段不连续
的物理地址映射成一段连续的虚拟地址,无论如何,其物理地址在每段之中是连续的,
这样一来,就不会造成CPU的CACHE频繁失效,从而大大节约时间

总结:
mmap 地址影射 包括 
A、内存物理地址 -- 虚拟地址
B、文件设备--虚拟地址 

目的是通过 虚拟地址访问 目标地址 ,目标地址 包括 物理地址 文件设备等


Logo

更多推荐