谈一谈mmap

来源:互联网 发布:淘宝助理初始化未响应 编辑:程序博客网 时间:2024/05/18 11:38

mmap是干什么的

一部分设备驱动提供mmap功能。这个功能是一个非常用意思的调用。他可以使用户空间应用程序直接通过所映射的地址空间访问设备的内存空间。我们知道内核内的所有线程、变量共享同一个地址空间。因此,从更广泛的意义上讲,这是一个非常有意义的用户空间和内核空间共享数据的方式。
在当前的内核设计过程中,我们把网络、文件系统等大数据交换或者存储的工作放在内核层来执行,以使这些具体的操作对用户来说是透明的。用户空间不需要关注文件系统的实现细节,不需要关注不同文件系统的差异性,直接采用POSIX接口就可以访问各种文件系统。这种设计模式的优势在于使各个子系统之间有效的进行隔离,便于不同层次子系统解决不同层次的问题。即,用户空间的程序解决业务逻辑的问题,内核空间的逻辑解决通用的数据组织的逻辑。然后,从另一个角度,这种隔离会降低系统的效率。例如,我们在基于网络的应用中,数据流是内核模块通过DMA机制先收到系统的内存中,接下来穿越IO栈,再进一步将数据复制到用户空间。注意到,这里不可避免的有一个复制的动作,在高速网络传输里面,这个复制的动作可以将性能降低到30%以上。
因此,在上述的一些应用场景中,为了避免硬件的性能被降低,在应用确定的情况下,我们可以不需要这操作系统所提供的这种便利,采用更高性能的处理方式。其中的一个处理方案是将内核中的一些文件系统、设备或者网络的处理逻辑放到用户空间里来处理。当前有大量的应用采用此种方式进行性能优化,比如用于网络数据处理的DPDK(http://www.dpdk.org/)。
设计类似于DPDK这样的应用程序时,我们需要解决的其中一个问题就是内核空间与用户空间通信的问题。这个通信需要满足以下几个条件:

条件一:用户空间和内核空间共享数据空间。数据放在指定的物理内存后,用户空间的应用程序和内核空间的设备驱动程序均能够访问到。
条件二:系统的罗辑足够简单,避免大量的系统调用。系统调用是用户空间使用内核空间的一个方式。当用户空间需要内核空间提供服务时,需要通过系统调用作为一个入口。系统调用服务程序会产生一个中断,使系统进行用户空间上下文(主要是运行时的堆栈、寄存器等信息)切换到内核空间。这个操作需要有时间损耗的。(这个切换时间的具体损耗可以自行测算)
条件三:能够直接或者间接提供一些同步机制使内核空间、用户空间对数据的访问是斥的
当时系统中可以解决用户空间和内核空间数据传递的方式很多,比如使用Proc、ioctl、sysfs、netlink等方式。前三者不能采用的原因主要是考虑其应用的专用性,另外,由于用户空间和内核空间指代的不是同 一个内存。在进行用户空间和内核空间通信时不可避免的会进行内存间的复制。
还有一种方式就是netlink,类似于用户空间进程与内核建立了一条流式数据通道。与上述方式类似,在传递大量数据共享时有其局限性。在此不多论述。

mmap系统调用

mmap系统调用原型如下:

#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,                  int fd, off_t offset);
  • addr代表新的映射的地址空间。这代表的地址空间是进程空间的地址空间。如果在调用时不指定这个值,将其设为NULL的话,内核会根据一定的规则选择一个地址空间进行映射。在实际应用过程中,这个工作交给操作系统比较好,我们无需知道地址空间的具体数值,只需要知道反回值代表表着我们已经映射的空间就可以了;
  • length代表需要映射的长度,以字节为单位。
  • prot用来描述映射的地址保护机制。最简单的方式是将所有的权限都打开,如此,你在访问时不会受到任何的障碍,在实际操作时,不建议采用此种方式。需要本着严谨负责的态度,需要哪些权限就打开哪些权限。这样的话,可以避免程序出现这样、那样的隐含的问题。
  • flags用来指定在映射之后,其他进程对所映射的访问情况。具体可以参考man文件。
  • offset指需要映射的空间在设备整体可映射空间的偏移,这个值主要是为了让内核的驱动程序知道应该给你哪些地址空间。需要注意的是这个数据需要以页面对齐的,否则系统会返回映射失败。在我们上节描述的情况下,这个值可以设为0.即将内核里所有的应该映射的空间从0开始映射出来。下一步,双方协商好所对应的数据结构就可以了。

内核里中的实现

内核中需要实现mmap的操作回调。一般编程模式是,首先定义并初如始化对应的驱动结构体。以混杂设备为例。定义如下:

struct miscdevice test_dev = {    MISC_DYNAMIC_MINOR,    MISC_DEV_NAME,    &test_fops};

其中代码中第4行的test_fops就是文件操作回调。我们声明如下:

static struct file_operations test_fops = {    .owner    = THIS_MODULE,    ……    .mmap    = test_mmap,};

接下来,将设备注册到系统,当用户空间打开这个设备并调用mmap之后。test_mmap就会被执行。
在执行test_mmap,内核会做很多其它的操作。其中比较对我们有影响的就是确定最终映射的进程内存地址空间、检查各方面的参数。在调试时,一担发现还没有到具体我们需要实现的回调,系统就返回错误时,重点检查系统调用的各项参数是否正确。
mmap回调原型如下:

int (*mmap) (struct file *filp, struct vm_area_struct *vma);

其中filp表示文件的描述符。通常情况下在回调里面需要特殊的处理。具体可以参考LDD中的描述。vma代表着一段虚拟地址的相关信息。在这时一大部分关于vma的初始化工作,内核已经完成了,这里需要我们做的就是将什么样的哪些地址给用户空间。
最简单的操作时,申请一段空间,然后将这段空间映射给用户空间。参考代码如下:

static int test_mmap(struct file *filp,struct vm_area_struct *vma){    char *mem = NULL;    mem = vmalloc_user(SIZE);    if(!mem){        //do something    }    if(remap_vmalloc_range(vma,mem,0)){        //do something    }}

映射方式

刚才在上节中,我们描述了用remap_vmalloc_range的方式将自己申请的一段内存空间映射到用户空间。除了上述方式外,内核还提供了将某个页框(pageframe)映射到用户空间的函数。

int remap_pfn_range(struct vm_area_struct *vma,unsigned long virt_addr, unsigned long pfn,unsigned long size, pgprot_t prot);

关于上述函数如何使用有大量的资料可以参考,不再掇述。

小结

mmap是用户空间和内核空间共享数据的一种高性能的方式,广泛应用在网络处理程序中
mmap使用过程中是将虚拟地址空间映射到用户空间。
虚拟地址空间、线性地址空间、用户地址空间的区别与联系可以参考资料 http://blog.csdn.net/do2jiang/article/details/4512417。(涉及到段页式内存管理)

参考资料

  1. Bovet D P. Understanding the Linux Kernel[M]. 东南大学出版社, 2006.
  2. 鲁比尼等著, 魏永明, 骆刚,等. Linux设备驱动程序, (第二版)[M]. 中国电力出版社, 2002.
  3. http://blog.csdn.net/do2jiang/article/details/4512417
原创粉丝点击