Linux内核--读书笔记之内存寻址
- 格式:doc
- 大小:564.50 KB
- 文档页数:5
《深入理解Linux内核》读书笔记 -----内存寻址
题记: 最近在看《深入理解linux内核》这本书,看的时候记了些笔记,主要是书上一些重要知识点的謫录还有就是自己的一些个人理解,其实一些地方我也没大看懂,还需要继续研究,先把东西记在这,一些地方记的也有点乱,主要是为以后自己再回去学习这方面知识做一些线索指导,也希望对大家学习这方面只是有些帮助,有什么不正确的地方还请指正
三种地址 逻辑地址(logical address): 包含机器语言指令中用来指定一个操作数或一条指令的地址 每一个逻辑地址都由一个段(segment)和偏移量(offset)组成
线性地址(又称虚拟地址): 是一个32位的无符号整数,可以用来表示高达4G的地址 通常用十六进制数字表示,值的范围从0x00000000到0xffffffff
物理地址: 与从微处理器的地址引脚发送到内存总线上的电信号有关
内存寻址的转换过程: 逻辑地址---(分段单元)--->线性地址---(分页单元)--->物理地址
硬件分段: GDT:全局描述符 LDT:局部描述符
逻辑地址由一个16位长的段选择符和32位的偏移量组成 为了快速方便的找到段选择符,处理器提供了段寄存器,段寄存器的 目的是存放段选择符
每个段由1个8字节的段描述符表示,它表述了段的特征,存放在GDT或LDT中 我们在寻址的时候,一般是从段选择符找到段描述符,然后从段描述符中取得段基址 加上偏移量就形成了我们要访问的地址 段选择符(16位)用来表示指向哪个段描述符,即用来在段描述符表(GDT,LDT)中寻址, 前13位是地址,能寻从0到2^13-1,因此段描述符表的大小是8192,后三位是一些特 权级的限制 段描述符是用来表示这个段的一些属性,如段基址和段长之类的 段选择符的后三位:index,TI,RPL 逻辑机制转换成线性地址的流程: 1)先检查段选择符的TI字段,以决定段描述符保存在哪一个描述符表中。TI字段(0或1)指明描述符是在GDT中还是在LDT中 2)从段选择符的index字段计算段描述符的地址,index字段的值乘以8(一个段描述符的大小), 这个结果在与gdtr或ldtr寄存器中的内容相加 3)把逻辑地址的偏移量与段描述符Base字段的值相加就得到了线性地址
Linux中的分段 分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间,与分段相比,linux更喜欢使用分页,因为: 1)当所有进程使用相同的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址 2)Linux设计目标之一是可以把他移植到绝大多数流行的处理器平台上。然而,RISC体系结构对分段的支持有限 Linux下所有段都是从0x00000000开始的,所以在linux下逻辑地址和线性地址是一致的(是一致,不是相同),即逻辑地址的偏移量字段的值和相应的线性地址的值是一致的。
硬件分页机制 分页是将线性地址转换成物理地址。 为了效率起见,线性地址被分成以固定长度为单位的组,称为页(page),在一个页面上的连续的地址也被映射到连续的物理地址上. 分页单元把所有的RAM分成固定程度的页框(也叫物理页),每个页框包含一个页(page)。页和页框是有区别的,前者只是一个数据块,可以存放在任何页框或磁盘中,而后者是一个存储区域
常规分页:从80386起,Intel处理器的分页单元处理4KB(2^12)的页 32位的线性地址被分成Directory(目录 10位),Table(页表,10位),Offset(偏移量 12位).线性地址的转换主要就是2种表的转换,页目录表(page directory)和页表(page table).
另外,每个活动的进程必须有一个分配给它的页目录,正在使用的页目录的物理地址存放在控制寄存器的CR3中,也就是说不同的进程CR3的值是不同的。
PAE(物理地址扩展)分页机制和64位的分页,原理上和常规分页是类似的,只是他们通过增加地址线使访问范围要大一些,还有就是一些页表和offset之类的设置不一样,由于用的也不多,我也没咋深入看,感兴趣的话,可以去网上找这方面的资料
为了缩小CPU和RAM之间的速度不匹配,引入了硬件高速缓存内存(hardware cache memory),它是基于著名的局部性原理的
除了硬件高速缓存(cache)外,80x86处理器还包含了另外一个称为转换后援缓冲器(TLB)的高速缓存用于加快线性地址的转换,加速页表的查找。当一个线性地址被第一次使用时,通过慢速访问的RAM中的页表计算出相应的物理地址。同时,物理地址被存放在一个TLB表项中,以便以后对同一个线性地址的引用可以快速的得到转换.
Linux的分页 Linux采用了一种同时适用于32位和64位系统的普通分页模型,在2.6.11之前,采用三级分页,2.6.11开始采用四级分页模型:页全局目录,页上级目录,页中间目录,页表。 通过设置一些目录是否为全0,来对应32位或是64位系统。Linux定义了很多宏和函数来做这方面的处理,在这就不多说了,有兴趣的话可以去看原书或是去网上找这方面的资料
物理地址布局: 在初始化阶段,内核必须建立一个物理地址映射来指定哪些物理地址范围对内核来说是可用的。Linux内核一般被加载在物理地址0x00100000开始的地方,即第二个MB开始的地方,因为之前1MB的空间需要留给BIOS之类的程序。 内核询问BIOS并了解内存的大小。 随后,内核执行machine_specific_memory_setup(void) 函数(include/asm-i386/mach-default/setup_arch_post.h中),该函数建立物理地址映射。 setup_memory()在machine_specific_memory_setup(void)之后被调用,它分析物理内存区域表并初始化一些变量来描述内核的物理内存布局.
进程页表: 进程的线性地址空间被分为两个部分: 从0x00000000到0xbfffffff(3GB)的用户态线性地址,0xc00000000到0xffffffff(1GB)内核态线性地址。宏PAGE_OFFSET的值是0xc0000000,这就是进程在线性地址空间的偏移量,也是内核空间的开始之处。
内核页表: 内核维持着一组自己使用的页表,驻留在所谓的主内核页全局目录中。 内核在刚刚被装入内存后,CPU仍然运行在实模式,所以分页功能没有被启用。 内核初始化页表分为两个阶段: 第一个阶段:内核创建一个有限的地址空间,包括内核的代码段和数据段,初始化页表和用于动态数据结构的共128KB大小的空间,该空间仅够将内核装入RAM和对其初始化的核心数据结构。 第二个阶段:内核充分利用剩余的RAM并适当的建立分页表。 临时页全局目录是在内核编译过程中静态初始化的,而临时页表是由startup_32()汇编语言函数(arch/i386/kernel/head.S)初始化的,此时的页上级目录和页中间目录相当于页全局目录项
临时页全局目录放在swapper_pg_dir变量中,临时页表放在pg0变量处开始存放,为简单起见,我们假设内核使用的段,临时页表和128KB的内存范围能容纳于RAM的前8M空间里 分页第一个阶段的目标是允许在实模式和保护模式下都能很容易的对这8MB寻址,因此,内核创建一个映射,把0x00000000~0x007fffff(8M)和0xc0000000~0xc07fffff(8M)的线性地址映射到0x00000000~0x007fffff的物理地址。
汇编语言函数startup_32()也启用分页单元,通过向cr3控制寄存器装入swapper_pg_dir的地址及设置cr0控制寄存器的PG标志来达到这一目的
对不同RAM的内核页表 1),当RAM小于896MB的时的最终内核页表 (注:这个896MB是用内核空间的1024MB减去128MB得来的,这128MB有别的用处,如非连续空间的物理映射,高端内存页框映射等,在以后的内存管理部分会继续讲) 由内核页表所提供的最终映射必须把从0xc0000000开始的线性地址转换为从0开始的物理地址。 宏__pa用于把从PAGE_OFFSE(0xc0000000)开始的线性地址转换成相应的物理地址,而__va做相反的转化(千万要注意,这个转换只适用于内核空间,用户空间有另外的转换方式)
2),当RAM大小在896MB和4096MB之间时的最终内核页表 这种情况下,并不把RAM全部映射到内核地址空间,Linux在初始化阶段可以做的最好的事是把一个具有896MB的RAM窗口映射到内核线性地址空间,如果一个程序需要对现有RAM的其余地址寻址,那就必须把其他的线性地址间隔映射到所需的RAM(在内存管理部分会继续深入讲解)
3)当RAM大于4096MB时的内核页表 还在看这部分,在平时的应用中很少用到大于4096MB的,主要是需要支持PAE的情况的,PAE处理36位物理地址,其实和之前的差不多,前面的896MB直接映射,剩余的留下来不映射,由动态重映射开处理。主要是需要支持PAE的情况的,PAE处理36位物理地址寻址空间也要大一些,所以使用的分页方式需要使用三级分页模型