Linux显存
在Linux中,用户显存和内核显存是独立的,在各自的地址空间实现。地址空间是虚拟的,就是说地址是从数学显存中具象下来的(通过一个简略描述的过程)。因为地址空间是虚拟的,所以可以存在好多。事实上,内核本身留驻在一个地址空间中,每位进程留驻在自己的地址空间。这种地址空间由虚拟显存地址组成,容许一些带有独立地址空间的进程指向一个相对较小的数学地址空间(在机器的数学显存中)。不仅仅是便捷,并且更安全。由于每位地址空间是独立且隔离的,因而很安全。
然而与安全性相关联的成本很高。由于每位进程(和内核)会有相同地址指向不同的化学显存区域,不可能立刻共享显存。辛运的是,有一些解决方案。用户进程可以通过PortableOperatingSystemInterfaceforUNIX®(POSIX)共享的显存机制(shmem)共享显存,但有一点要说明,每位进程可能有一个指向相同数学显存区域的不同虚拟地址。
虚拟显存到化学显存的映射通过页表完成,这是在底层软件中实现的(见图1)。硬件本身提供映射,并且内核管理表及其配置。注意这儿的显示,进程可能有一个大的地址空间,并且极少见,就是说小的地址空间的区域(页面)通过页表指向化学显存。这容许进程仅为随时须要的网页指定大的地址空间。
图1.页表提供从虚拟地址到化学地址的映射
因为缺少为进程定义显存的能力,底层化学显存被过度使用。通过一个称为paging(但是,在Linux中一般称为swap)的进程,极少使用的页面将手动移到一个速率较慢的储存设备(例如c盘),来容纳须要被访问的其它页面(见图2)。这一行为准许,在将极少使用的页面迁移到c盘来提升数学显存使用的同时,计算机中的数学显存为应用程序更容易须要的页面提供服务。注意,一些页面可以指向文件,在这些情况下,假如页面是脏(dirty)的,数据将被冲洗,假如页面是干净的(clean),直接扔掉。
图2.通过将极少使用的页面迁移到速率慢且实惠的储存器,交换使化学显存空间得到了更好的借助
MMU-less构架
不是所有的处理器都有MMU。因而,uClinux发行版(微控制器Linux)支持操作的一个地址空间。该构架缺少MMU提供的保护,并且容许Linux运行另一类处理器。关于uClinux的详尽信息见。
选择一个页面来交换储存的过程被称为一个页面置换算法,可以通过使用许多算法(起码是近来使用的)来实现。该进程在恳求储存位置时发生,储存位置的页面不在储存器中(在储存器管理单元[MMU]中无映射)。这个风波被称为一个页面错误并被硬件(MMU)删掉,出现页面错误中断后该风波由防火墙管理。该栈的详尽说明见。
Linux提供一个有趣的交换实现linux用户空间初始化,该实现提供许多有用的特点。Linux交换系统容许创建和使用多个交换分区和优先权,这支持储存设备上的交换层次结构,这种储存设备提供不同的性能参数(比如,固态c盘[SSD]上的一级交换和速率较慢的储存设备上的较大的二级交换)。为SSD交换附加一个更高的优先级使其可以使用直到用尽;直至那时,页面能够被写入优先级较低的交换分区。
图3.地址空间和虚拟-化学地址映射的元素
并不是所有的页面都适宜交换。考虑到响应中断的内核代码或则管理页表和交换逻辑的代码,即便linux是什么,这种页面决不能被换出,因而它们是固定的,或则是永久地留驻在显存中。虽然内核页面不须要进行交换,可是用户页面须要,并且它们可以被固定,通过mlock(或mlockall)函数来锁定页面。这就是用户空间显存访问函数的目的。假如内核假定一个用户传递的地址是有效的且是可访问的,最终可能会出现内核严重错误(kernelpanic)(比如,由于用户页面被换出,而造成内核中的页面错误)。该应用程序编程插口(API)确保那些边界情况被妥善处理。
内核API
如今,让我们来研究一下用户操作用户显存的内核API。请注意,这涉及内核和用户空间插口,而下一部份将研究其他的一些显存API。用户空间显存访问函数在表1中列举。
表1.用户空间显存访问API函数描述
access_ok
检测用户空间显存表针的有效性
get_user
从用户空间获取一个简单变量
put_user
输入一个简单变量到用户空间
clear_user
去除用户空间中的一个块,或则将其归零。
copy_to_user
将一个数据块从内核复制到用户空间
copy_from_user
将一个数据块从用户空间复制到内核
strnlen_user
获取显存空间中字符串缓冲区的大小
strncpy_from_user
从用户空间复制一个字符串到内核
正如您所期望的,这种函数的实现构架是独立的。诸如在x86构架中,您可以使用./linux/arch/x86/lib/usercopy_32.c和usercopy_64.c中的源代码找到这种函数以及在./linux/arch/x86/include/asm/uaccess.h中定义的字符串。
当数据联通函数的规则涉及到复制调用的类型时(简单VS.集聚),这种函数的作用如图4所示。
图4.使用UserSpaceMemoryAccessAPI进行数据联通
access_ok函数
您可以使用access_ok函数在您想要访问的用户空间检测表针的有效性。调用函数提供指向数据块的开始的表针、块大小和访问类型(无论这个区域是拿来读还是写的)。函数原型定义如下:
access_ok( type, addr, size );
type参数可以被指定为VERIFY_READ或VERIFY_WRITE。VERIFY_WRITE也可以辨识显存区域是否可读以及可写(虽然访问依然会生成-EFAULT)。该函数简单检测地址可能是在用户空间,而不是内核。
get_user函数
要从用户空间读取一个简单变量,可以使用get_user函数,该函数适用于简单数据类型,例如,char和int,并且像结构体这类较大的数据类型,必须使用copy_from_user函数。该原型接受一个变量(储存数据)和一个用户空间地址来进行Read操作:
get_user( x, ptr );
get_user函数将映射到两个内部函数其中的一个。在系统内部,这个函数决定被访问变量的大小(按照提供的变量储存结果)并通过__get_user_x产生一个内部调用。成功时该函数返回0,通常情况下,get_user和put_user函数比它们的块复制副本要快一些,倘若是小类型被联通的话,应当用它们。
put_user函数
您可以使用put_user函数来将一个简单变量从内核写入用户空间。和get_user一样,它接受一个变量(包含要写的值)和一个用户空间地址作为写目标:
put_user( x, ptr );
和get_user一样什么是linux,put_user函数被内部映射到put_user_x函数,成功时,返回0,出现错误时,返回-EFAULT。
clear_user函数
clear_user函数被用于将用户空间的显存块清零。该函数采用一个表针(用户空间中)和一个机型进行清零,这是以字节定义的:
clear_user( ptr, n );
在内部linux用户空间初始化,clear_user函数首先检测用户空间表针是否可写(通过access_ok),之后调用内部函数(通过内联组装形式编码)来执行Clear操作。使用带有repeat前缀的字符串指令将该函数优化成一个特别紧密的循环。它将返回不可消除的字节数,假若操作成功,则返回0。
copy_to_user函数
copy_to_user函数将数据块从内核复制到用户空间。该函数接受一个指向用户空间缓冲区的表针、一个指向显存缓冲区的表针、以及一个以字节定义的厚度。该函数在成功时,返回0,否则返回一个非零数,强调不能发送的字节数。
copy_to_user( to, from, n );
检测了向用户缓冲区写入的功能以后(通过access_ok),内部函数__copy_to_user被调用,它反过来调用__copy_from_user_inatomic(在./linux/arch/x86/include/asm/uaccess_XX.h中。其中XX是32或则64,具体取决于构架。)在确定了是否执行1、2或4字节复制过后,该函数调用__copy_to_user_ll,这就是实际工作进行的地方。在受损的硬件中(在i486之前,WP位在管理模式下不可用),页表可以随时替换,须要将想要的页面固定到显存,使它们在处理时不被换出。i486以后,该过程只不过是一个优化的副本。
copy_from_user函数
copy_from_user函数将数据块从用户空间复制到内核缓冲区。它接受一个目的缓冲区(在内核空间)、一个源缓冲区(从用户空间)和一个以字节定义的宽度。和copy_to_user一样,该函数在成功时,返回0,否则返回一个非零数,强调不能复制的字节数。
copy_from_user( to, from, n );
该函数首先检测从用户空间源缓冲区读取的能力(通过access_ok),之后调用__copy_from_user,最后调用__copy_from_user_ll。自此开始,按照架构,为执行从用户缓冲区到内核缓冲区的零拷贝(不可用字节)而进行一个调用。优化组装函数包含管理功能。
strnlen_user函数
strnlen_user函数也能像strnlen那样使用,但前提是缓冲区在用户空间可用。strnlen_user函数带有两个参数:用户空间缓冲区地址和要检测的最大厚度。
strnlen_user( src, n );
strnlen_user函数首先通过调用access_ok检测用户缓冲区是否可读。若果是strlen函数被调用,maxlength参数则被忽视。
strncpy_from_user函数
strncpy_from_user函数将一个字符串从用户空间复制到一个内核缓冲区,给定一个用户空间源地址和最大厚度。
strncpy_from_user( dest, src, n );
因为从用户空间复制,该函数首先使用access_ok检测缓冲区是否可读。和copy_from_user一样,该函数作为一个优化组装函数(在./linux/arch/x86/lib/usercopy_XX.c中)实现。
显存映射的其他模式
前面部份阐述了在内核和用户空间之间联通数据的方式(使用内核初始化操作)。Linux还提供一些其他的方式,用于在内核和用户空间中联通数据。虽然这种方式未必才能提供与用户空间显存访问函数相同的功能,并且它们在地址空间之间映射显存的功能是相像的。
在用户空间,注意,因为用户进程出现在单独的地址空间,在它们之间联通数据必须经过某种进程间通讯机制。Linux提供各类模式(例如,消息队列),并且最知名的是POSIX共享显存(shmem)。该机制准许进程创建一个显存区域,之后同一个或多个进程共享该区域。注意,每位进程可能在其各自的地址空间中映射共享显存区域到不同地址。因而须要相对的轮询偏斜(offsetaddressing)。
mmap函数容许一个用户空间应用程序在虚拟地址空间中创建一个映射,该功能在某个设备驱动程序类中是常见的,容许将化学设备显存映射到进程的虚拟地址空间。在一个驱动程序中,mmap函数通过remap_pfn_range内核函数实现,它提供设备显存到用户地址空间的线性映射。
结束语
本文讨论了Linux中的显存管理主题,之后讨论了使用这种概念的用户空间显存访问函数。在用户空间和内核空间之间联通数据并没有表面上看上去这么简单,并且Linux包含一个简单的API集合,跨平台为您管理这个复杂的任务。