内核空间和用户空间
用户空间:在Linux中,每个用户进程都可以访问4GB的线性虚拟内存空间。其中从0到3GB的虚存地址是用户空间,通过每个进程自己的页目录、页表,用户进程可以直接访问。
内核空间:从3GB到4GB的虚存地址为内核态空间,存放供内核访问的代码和数据,用户态进程不能访问,只有内核态进程才能寻址。所有进程从3GB到4GB的虚拟空间都是一样的,linux以此方式让内核态进程共享代码段和数据段。
由于虚拟机制的引入,进程的可以使用32位地址系统支持的全部4G线性空间。进程的线性地址空间分成两部分:
- 从0x00000000 到 0xbfffffff的线性地址,无论用户态还是内核态的进程都可以寻址。
- 从0xc0000000 到 0xffffffff的线性地址,只有内核态的进程才能寻址。
当进程运行在用户态时,它产生的线性地址小于0xc0000000;当进程运行在内核态时,它执行内核代码,所产生的线性地址大于等于0xc0000000、且所有进程共享。如果是多核CPU,那么就会出现多个进程并发访问这些地址的现象,这里就要牵扯到临界区资源,也就是我们将来在介绍同步与互斥内容中要重点讨论的。宏PAGE_OFFSET产生的值就是0xc0000000,这就是进程在线性地址空间中的偏移量,也是内核生存空间的开始之处。
页全局目录被分成了两部分,的第一部分表项映射的线性地址小于0xc0000000(共1024项,在PAE未启用时是前768项,PAE启动时是前3项),具体大小依赖特定进程。相反,剩余的表项对所有进程来说都应该是相同的,它们等于主内核页全局目录的相应表项。
那么,什么又是主内核页全局目录呢?内核维持着一组自己使用的页表,驻留在所谓主内核页全局目录(master kernel Page Global Directory)中。系统初始化后,这组页表还从未被任何进程或任何内核线程直接使用,主要用来为系统中每个普通进程对应的页全局目录项提供参考模型。
内核如何初始化自己的页表?这个过程分为两个阶段。事实上,内核映像刚刚被装入内存后,CPU仍然运行于实模式,所以分页功能没有被启用。 第一个阶段,内核创建一个有限的地址空间,包括内核的代码段和数据段、初始页表和用于存放动态数据结构的共128KB大小的空间。这个最小限度的地址空间仅足够将内核装入RAM和对其初始化的核心数据结构。 第二个阶段,内核充分利用剩余的RAM并适当地建立分页表。
Linux内核地址映射模型
x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。
段页式机制如下图。
Linux内核地址空间划分
通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。
Linux内核高端内存的由来
当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物理地址为0×3,0xc0000004对应的物理地址为0×4,… …,逻辑地址与物理地址对应的关系为
物理地址 = 逻辑地址 – 0xC0000000