学习linux的内存管理(一)

前几天刚考完嵌入式操作系统,乘着这股热劲又学习了一下linux的内存管理,以下是的我的一些心得:

1,MMU。

    大家都知道,每个进程之间是独立的,并且可以访问4G的线性空间,那么怎样才能保持进程的独立性了,这就引入了MMU,即内存管理单元。MMU其实是一个硬件,在拥有MMU的体系结构中,内存被分为虚拟内存和物理内存,物理内存就是实际的内存大小,而应用程序使用虚拟内存,当访问内存数据时,由MMU根据页表将使虚拟内存地址转化为物理内存地址。

    MMU把各个进程的虚拟内存映射到不同的物理内存上,这样就能保证进程的虚拟内存地址是独立的。由于物理内存远远少于各个进程的虚拟内存的总和,操作系统会把暂时不用的内存数据写到磁盘上去,把腾出来的物理内存分配给有需要的进程使用。一般会在安装操作系统时在磁盘上创建一个专门的分区存放换出的内存数据,这个分区称为交换分区(Swap)。

    从虚拟内存到物理内存的映射并不是一个字节一个字节映射的,而是以一个称为页(page)最小单位的进行的。页的大小视具体硬件平台而定,通常是4K。当应用程序访问的虚拟内存的页面不在物理内存里时,MMU产生一个缺页中断,并挂起当前进程,缺页中断处理函数负责把相应的数据从磁盘读入内存,然后唤醒挂起的进程。

2,在分析linux的分页机制前,先来了解一下cpu的段式内存管理。

    段是虚拟地址空间的基本单位,段机制必须把虚拟地址空间的一个地址转换为线性地址空间的一个线性地址。在这里,段机制就是将逻辑地址转化为线性地址。

    逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址,一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],段标识符就是段选择符,通过它的索引号在段描述符表中查找相应的段,并从段描述符的结构体里得出相应段的基地址,线性地址=段基址+段内偏移地址,以下为段标识符的结构:

 

Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。

    GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。其中每个段描述符的大小为8个字节,以数组形式保存在段描述符表中。

    下图为逻辑地址转化为线性地址的过程图:

   首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
   1)、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
   2)、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
   3)、把Base + offset,就是要转换的线性地址了。

3,linux的段式管理

   由于绝大多数硬件平台都不支持段机制,只支持分页机制,所以为了让Linux具有更好的可移植性,我们需要去掉段机制而只使用分页机制。但不幸的是,IA32规定段机制是不可禁止的,因此不可能绕过它直接给出线性地址空间的地址。万般无奈之下,Linux的设计人员干脆让段的基地址为0,而段的界限为4GB,这时任意给出一个偏移量,则等式为“0+偏移量=线性地址”,也就是说“偏移量=线性地址”。另外由于段机制规定“偏移量 < 4GB”,所以偏移量的范围0H~FFFFFFFFH,这恰好是线性地址空间范围,也就是说虚拟地址直接映射到了线性地址,我们以后所提到的虚拟地址和线性地址指的也就是同一地址。看来,Linux在没有回避段机制的情况下巧妙地把段机制给绕过去了。
   另外,由于IA32段机制还规定,必须为代码段和数据段创建不同的段,所以Linux必须为代码段和数据段分别创建一个基地址为0,段界限为4GB的段描述符。不仅如此,由于Linux内核运行在特权级0,而用户程序运行在特权级别3,根据IA32的段保护机制规定,特权级3的程序是无法访问特权级为0的段的,所以Linux必须为内核和用户程序分别创建其代码段和数据段。这就意味着Linux必须创建4个段描述符——特权级0的代码段和数据段,特权级3的代码段和数据段。

 

Linux在启动的过程中设置了段寄存器的值和全局描述符表GDT的内容,段的定义在
include/asm-i386/segment.h中:
#define __KERNEL_CS 0x10 /*内核代码段,index=2,TI=0,RPL=0*/
#define __KERNEL_DS 0x18 /*内核数据段, index=3,TI=0,RPL=0*/
#define __USER_CS 0x23 /*用户代码段, index=4,TI=0,RPL=3*/
#define __USER_DS 0x2B /*用户数据段, index=5,TI=0,RPL=3*/
   从定义看出,没有定义堆栈段,实际上,Linux内核不区分数据段和堆栈段,这也体现了Linux内核尽量减少段的使用。因为没有使用LDT,因此,TI=0,并把这4个段都放在GDT中, index就是某个段在GDT表中的下标。内核代码段和数据段具有最高特权,因此其RPL为0,而用户代码段和数据段具有最低特权,因此其RPL为3。可以看出,Linux内核再次简化了特权级的使用,使用了两个特权级而不是4个。


全局描述符表的定义在arch/i386/kernel/head.S中:
ENTRY(gdt_table)
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* not used */
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at
0x00000000 */
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at
0x00000000 */
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at
0x00000000 */
.quad 0x00cff2000000ffff /* 0x2b user 4GB data at
0x00000000 */
.quad 0x0000000000000000 /* not used */
.quad 0x0000000000000000 /* not used */
/*
* The APM segments have byte granularity and their bases
* and limits are set at run time.
*/
.quad 0x0040920000000000 /* 0x40 APM set up for bad BIOS's
*/
.quad 0x00409a0000000000 /* 0x48 APM CS code */
.quad 0x00009a0000000000 /* 0x50 APM CS 16 code (16 bit) */
.quad 0x0040920000000000 /* 0x58 APM DS data */
.fill NR_CPUS*4,8,0 /* space for TSS's and LDT's */
   从代码可以看出,GDT放在数组变量gdt_table中。按Intel规定,GDT中的第一项为空,这是为了防止加电后段寄存器未经初始化就进入保护模式而使用GDT的。第二项也没用。从下标2到5共4项对应于前面的4种段描述符值。从描述符的数值可以得出:
· 段的基地址全部为0x00000000
· 段的上限全部为0xffff
· 段的粒度G为1,即段长单位为4KB
· 段的D位为1,即对这四个段的访问都为32位指令
· 段的P位为1,即四个段都在内存。
   从逻辑上说,Linux巧妙地绕过了逻辑地址到线性地址的映射,但实质上还得应付Intel所提供的段机制。只不过,Linux把段机制变得相当简单,它只把段分为两种:用户态(RPL=3)的段和内核态(RPL=0)的段,因此,描述符投影寄存器的内容很少发生变化,只在进程从用户态切换到内核态或者反之时才发生变化。另外,用户段和内核段的区别也仅仅在其RPL不同,因此内核根本无需访问描述符投影寄存器,当然也无需访问GDT,而仅从段寄存器的最低两位就可以获取RPL的信息。Linux这样设计所带来的好处是显而易见的,Intel的分段部件对Linux性能造成的影响可以忽略不计。
   在上面描述的GDT表中,紧接着那四个段描述的两个描述符被保留,然后是四个高级电源管理(APM)特征描述符,对此不进行详细讨论。按Intel的规定,每个进程有一个任务状态段(TSS)和局部描述符表LDT,但Linux也没有完全遵循Intel的设计思路。如前所述,Linux的进程没有使用LDT,而对TSS的使用也非常有限,每个CPU仅使用一个TSS。通过上面的介绍可以看出,Intel的设计可谓周全细致,但Linux的设计者并没有完全陷入这种沼泽,而是选择了简洁而有效的途径,以完成所需功能并达到较好的性能为目标。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值