本文基于mstar801平台Linux2.6.35.11。
前面我们分析了S3C2440A的地址空间《micro2440开发》第一章:S3C2440A地址空间,即ARM硬件的地址空间;作为对比学习,下面我们看看Linux操作系统即软件定义的地址空间:
====================================================================================================================================
一、为什么需要虚拟内存管理?
第一:让每个进程有独立的地址空间是引入虚拟内存管理的最主要目的。所谓的独立地址空间是指:不同进程中的同一个VA被MMU映射到不同的PA,并且在某一个进程中访问任何地址都不可能访问到另外一个进程的数据。
第二:让每个进程有独立的4GB空间,编写程序也会比较方便,不必为每个进程分配一个地址范围。
第三:引入VA到PA的映射也会给分频和释放连续内存带来方便。
第四:一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际可用的物理内存;虚拟内存管理使得这种情况下各进程仍然能够正常运行。因为各进程分配的虚拟内存页可以映射到物理内存的页框,也可以临时保存到磁盘上、当用到这些页框时再从磁盘加载回内存;专业术语叫“换页”。
第五:虚拟内存管理可以控制物理页面的访问权限。
==============================================================================
32位ARM-Linux操作系统可寻址4GB的虚拟地址空间;Linux内核将这4GB的空间分为两部分:最高的1GB(从虚拟地址0xC000 0000到0xFFFF FFFF)供内核使用、称为“内核地址空间”,较低的3GB(从虚拟地址0x0000 0000到0xBFFF FFFF)供进程使用,称为“用户地址空间”。因为每个进程都可以通过系统调用进入内核,因此、Linux内核空间由系统内所有进程共享。于是,每个进程可以拥有4GB的虚拟地址空间(也叫虚拟内存)。
任意一时刻,在ARM上只有一个进程在运行。所以,对于ARM来说:在任一时刻,整个系统只存在一个4GB的虚拟地址空间,这个虚拟地址空间是面向此进程的。当进程发生切换时,虚拟地址空间也随之切换。
但任何应用程序给出的虚拟地址最终都必须被转化为物理地址;所以,虚拟地址空间必须被映射到物理内存空间中,这个映射关系需要通过ARM体系结构中规定的数据结构来建立。这就是段描述符表和页表,Linux主要通过页表来进行映射。
因此,对于ARM-Linux操作系统来说我们要为每一个进程建立其页表。
1.内核空间的虚拟地址
内核空间占据了每个虚拟地址空间中的最高1GB,但映射到物理内存却总是从最低(0x0000 0000)开始;之所以这么规定,是为了在内核空间与物理内存之间建立简单的线性映射关系。其中3GB(0xC000 0000)就是物理地址与内核空间虚拟地址间的位移量,在Linux代码中就叫做PAGE_OFFSET。
kernel2.6.35.11/arch/arm/Kconfig
config PAGE_OFFSET hex default 0x40000000 if VMSPLIT_1G default 0x80000000 if VMSPLIT_2G default 0xC0000000kernel2.6.35.11/arch/arm/include/asm/memory.h
#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
另外,内核在RAM中的地址偏移也是制定的:kernel2.6.35.11/arch/arm/Makefile
textofs-y := 0x00008000 ...... TEXT_OFFSET := $(textofs-y)
====================================================================================================================================
从用户态指针与内核态指针分析内核地址空间与用户地址空间?
内核函数copy_from_user与copy_to_user的使用是结合进程上下文的:如果在驱动中使用这两个函数,必须是在实现系统调用的函数中使用,不可在实现中断处理的函数中使用(因为中断会有进程上下文切换)。其次由于操作的页面可能被换出,这两个函数可能会休眠,所以不可在中断上下文中使用。
分析:在同一进程上下文的用户态和内核态;内核态是可以直接访问用户态的虚拟内存地址的,但反之则不行、因为用户态的虚拟地址被限定在0~3GB。
因此,我们明白:进程上下文 = 用户级上下文 + 寄存器上下文 + 系统级上下文(内存管理信息);系统调用其实是内核运行在调用进程的进程上下文中。