Linux采用虚拟显存技术,系统中的所有进程之间以虚拟形式共享显存。对每位进程来说,它们似乎都可以访问整个系统的所有数学显存。更重要的是,虽然单独一个进程,它拥有的地址空间也可以远远小于系统数学显存。
进程地址空间由每位进程中的线性地址区组成,每位进程都有一个32位或64位的平坦(flat)空间,空间的具体大小取决于体系结构。”平坦”指地址空间范围是一个独立的连续区间。一般情况下,每位进程都有惟一的这些平坦空间,并且每位进程的地址空间之间彼此互不相干。两个不同的进程可以在它们各自地址空间的相同地址显存储存不同的数据。并且进程之间也可以选择共享地址空间,我们称这样的进程为线程。
在地址空间中,我们更为关心的是进程有权访问的虚拟显存地址区间,例如08048000~0804c000。那些可被访问的合法地址区间被成为显存区域(memoryarea),通过内核,进程可以给自己的地址空间动态地添加或减轻显存区域。
进程只能访问有效范围内的显存地址。每位显存区域也具有相应进程必须遵守的特定访问属性,如只读、只写、可执行等属性。假如一个进程访问了不在有效范围中的地址,或以不正确的形式访问有效地址,这么内核都会中止该进程,并返回”段错误”信息。
? 显存区域可以包含各类显存对象LINUX社区,如下:
? 可执行文件代码的显存映射,成为代码段(textsection)。
? 可执行文件的已初始化全局变量的显存映射,成为数据段(datasection)。
? 包含未初始化全局变量的零页(也就是bss段)的显存映射。零页是指页面中的数据全部为0。
? 用于进程用户空间栈的零页的显存映射。

? 每一个例如C库或动态链接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间。
? 任何显存映射文件。
? 任何共享显存段。
? 任何匿名的显存映射,例如由malloc()分配的显存。
进程地址空间的任何有效地址都只能坐落惟一的区域,这种显存区域不能互相覆盖。可以见到,在执行的进程中,每位不同的显存片段都对应一个独立的显存区域:栈、对象代码、全局变量、被映射的文件等等。
内核使用显存描述符表示进程的地址空间。显存描述符由mm_struct结构体表示,定义在文件中,该结构包含了和进程地址空间有关的全部信息。
VMA
显存区域由vm_area_struct结构体描述,定义在文件中,显存区域在内核中也常常被叫做虚拟显存区域或则VMA。
VMA标志是一种位标志,它定义在vm_area_struct结构中(该结构中的vm_flags子域)。和化学页的访问权限不同,VMA标志反映了内核处理页面索须要遵循的行为准则,而不是硬件要求。VM_IO标志显存区域中包含对设备I/O空间的映射。该标志一般在设备驱动程序执行mmap()函数进行I/O空间映射时才被设置,同时该标志也表示该显存区域不能被包含在任何进程的储存转存(coredump)中。VM_RESERVED标志显存区域不能被换出,它也是在设备驱动程序进行映射时被设置。
vm_area_struct结构体中的vm_ops域指向与指定显存区域相关的操作函数表,内核使用表中的方式操作VMA。

mmap()和do_mmap():创建地址区间
内核使用do_mmap()函数创建一个新的线性地址区间。并且说给函数创建一个新VMA并不十分确切,由于假如创建的地址区间和一个早已存在的地址区间相邻,而且它们具有相同的访问权限的话,这么两个区间将合并为一个。若果不能合并,这么就确实须要创建一个新的VMA了。但无论哪种情况,do_mmap()函数就会将一个地址区间加入到进程的地址空间中——无论是扩充早已存在的显存区域还是创建一个新的区域。
do_mmap()函数申明在文件中,原型如下:
unsignedlongdo_mmap(structfile*file,unsignedlongaddr,
unsignedlonglen,unsignedlongprot,
unsignedlongflag,unsignedlongoffset)
在用户空间可以通过mmap()函数调用获取内核函数do_mmap()的功能。mmap()系统调用原型如下:
void*mmap2(void*start,size_tlength,
intprot,intflags,
intfd,off_tpgoff)

do_munmap()函数从特定的进程地址空间中删掉指定地址区间,该函数在文件中申明:
intdo_munmap(structmm_struct*mm,unsignedlongstart,size_tlen)
系统调用munmap()给用户空间程序提供了一种从自身地址空间中删掉指定地址区间的方式,它和系统调用mmap()的作用相反:
intmunmap(void*start,size_tlength)
mmap设备操作
对于驱动程序来说,显存映射可以提供给用户程序直接访问设备显存的能力。映射一个设备,意味着使用户空间的一段地址关联到设备显存上。无论何时,只要程序在分配的地址范围内进行读取或则写入,实际上就是对设备的访问。
并不是所有的设备都能进行mmap具象。诸如,并口设备和其他面向流的设备就难以实现这些具象。mmap的另一个限制是映射都是以PAGE_SIZE为单位的。内核只能在页表一级处理虚拟地址;为此,被映射的区域必须是PAGE_SIZE的整数倍,并且必须坐落起始于PAGE_SIZE整数倍地址的数学显存内。假如区域的大小不是页大小的整数倍,内核就通过生成一个稍为大一些的区域来容纳它。
mmap方式是file_operations结构中的一员,但是在执行mmap系统调用时都会调用该技巧。在调用实际方式之前,内核会完成好多工作,并且该方式的原型与系统调用的原型由很大区别。
文件操作申明如下:
int(*mmap)(structfile*filp,structvm_area_struct*vma);

其中vma参数包含了用于访问设备的虚拟地址区间的信息。大部份工作早已由内核完成了,要实现mmap,驱动程序只要为这一地址范围构造合适的页表即可,假如须要的话,就用一个新的操作集替换vma->vm_ops。
有两种构建页表的方式:使用remap_page_range函数可一次构建所有的页表,或则通过nopageVMA方式每次构建一个页表。
#p#副标题#e#
构造用于映射一段化学地址的新页表的工作是由remap_page_range完成的,它的原型如下:
intremap_page_range(unsignedlongvirt_add,unsignedlongphys_add,
unsignedlongsize,pgprot_tprot);
nopage方式具有如下原型:
structpage(*nopage)(structvm_area_struct*vma,unsignedlongaddress,intwrite_access);
当用户进程访问当前不在显存中的VMA页面时,都会调用关联的VMA操作集中的nopage函数。参数address包含造成失效的虚拟地址,该地址向上取整到所在页的起始地址。函数nopage必须定位并返回指向用户所期望的页的structpage表针。这个函数还要调用get_page宏sogou pinyin linux,以降低它所返回的页面的使用计数:get_page(structpage*pageptr)。
nopage方式在mremap系统调用中十分有用。应用程序使用mremap来改变应当映射区域的边界地址。mremap系统调用的原型如下:
void *mremap(void *old_address, size_told_size,size_tnew_size,unsignedlongflags);
Linux的mremap实现没有通知驱动程序被映射区域的变化。事实上,当区域减少时,它会通过unmap方式通知驱动程序;但区域变大时,却没有相应的反弹函数可以借助。
换句话说,在映射区域下降时驱动程序不会得到通知,由于nopage方式会在将来完成相应的工作,因而毋须在真正须要之前使用显存。为此,假如我们须要支持mremap系统调用linux mmap 文件,就必须实现nopage技巧。
nopage方式必要的实现步骤如下:
首先估算想得到的数学地址,之后用__va()将它转换为逻辑地址,最后用virt_to_page将逻辑地址转成一个structpage,返回指向这个structpage的表针。通常来说,直接从数学地址获得structpage是可能的linux mmap 文件,并且这样的代码很难在不同的体系结构之间移植。
remap_page_range的一个有意思的限制是,它只能对保留页和化学显存之外的数学地址给与访问。也就是说,remap_page_range不会容许你重映射常规地址(常规数学显存对应的地址),相反,它会改为映射到零页。为此,假如须要映射RAM的话,我们只能使用nopage方式。
下边是对simple.c(ldd2的样例代码)得一个简单的剖析:
/*
*Simple-REALLYsimplememorymapping
