linux简化了分段机制,使得虚拟地址与线性地址总是一致的。线性空间在32为平台上为4GB的固定大小,也就是Linux的虚拟地址空间也这么大,Linux内核将这4GB的空间分为两个部分。最高的1GB(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间“。而较低的3GB(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间”,因为每个进程可以通过系统调用进入内核,因此,Linux内核空间由系统内的所有进程共享。所以,每个进程可以拥有4GB的虚拟地址空间(也叫虚拟内存)。每个进程有各自的私有用户空间(0~3GB),这个空间对系统中的其他进程是不可见的。最高的1GB内核空间则为所有进程以及内核所共享(虽然共享,但是一个完整的虚拟地址空间就是4GB,内核空间是每个进程都要被分配的,只是他们对应相同的地址空间)。
进程的线性地址空间分为两部分:
1、从0×00000000到0xbfffffff的线性地址,无论进程运行在用户态还是内核态都可以寻址。
2、从0xc0000000到0xffffffff的线性地址,只有内核态的进程才能寻址。
用户空间不是被进程共享的,而是被进程隔离的。每个进程最大可以有3GB用户空间。所以说一个进程对一个地址的访问,与另一个进程对同一个地址的访问不冲突,因为尽管是同一个地址但因为,进程的用户空间不共享 导致他们其实并没有指向同一个地址。而对于cpu来讲,在任意的时刻,整个系统都只有4GB的虚拟地址空间,这个虚拟空间是面向进程的,所以当进程切换的时候,虚拟地址空间也会切换。所以只有此进程运行的时候,其虚拟地址空间才被CPU所知。其他时刻,其虚拟空间不被CPU所知。
一个程序编译链接后形成的是虚拟地址空间,而程序最终要运行在物理内存中。所以,虚拟地址空间必须被映射到物理内存空间中,这个映射关系需要通过硬件体系结构所规定的数据结构来建立。即段描述符表和页表,而Linux主要通过页表来进行映射。
由以上得出一个结论。如果给出的页表不同,那么CPU将某一虚拟地址空间中的地址转化成的物理地址也不同,所以每个进程都建立了页表,将每个进程的虚拟地址空间根据自己的需要映射到物理地址空间上。既然在一个时刻CPU上只能有一个进程在运行,那么当进程发生切换时,将页表也更换为相应进程的页表,这就可以实现每个进程都有自己的虚拟地址空间而互不影响。
内核空间到物理空间内存的映射
内核空间占据了每个虚拟空间中的最高1GB,但映射到物理内存却总是从最低的地址(0x00000000)开始的,所以3GB(0xC0000000)就是物理地址与虚拟地址之间的位移量,而在Linux代码中就叫做PAGE_OFFSET。
在page.h 中对内核空间中地址映射的说明及定义如下:
# define _PAGE_DFFSET (0xC0000000)
........
#define PAGE_OFFSET ((unsigner long) __PAGE_OFFSET)
#define _pa(x) (unsigned long) (x) - PAGE_OFFSET
#define _va(x) ((void *) ((unsigned long) (x) + PAGE_OFFSET))
对于内核空间而言,给定一个虚地址x,其物理地址为x-PAGE_OFFSET,给定一个物理地址x,其虚地址为x+PAGE_OFFSET。
这只适合内核空间的虚地址映射到物理地址,而绝不适用于用户空间。