Linux内核源码分析--内存管理(一、分页机制)

        Linux系统中分为几大模块:进程调度、内存管理、进程通信、文件系统、网络模块;各个模块之间都有一定的联系,就像蜘蛛网一样,所以这也是为什么Linux内核那么难理解,因为不知道从哪里开始着手去学习。很多人会跟着系统上电启动 BIOS-->bootsect-->setup-->head-->main-->.....来学习,但是最后会发现当你在看main的时候你必须知道其他模块大概工作情况,要不然根本不知道为什么要这么做(也许其中的C代码和汇编你都能看懂,但真正含义其不知道)。注意下:下面的blog中涉及到的操作系统都是选择0.11版的Linux系统


前言

        所幸的是我最开始入手选择了内存管理,而内存管理和其他模块联系就相对小一些(没看完其他模块,但感觉和其他模块联系不是很大),只有页面中断和进程那两个模块有些关系。好了,现在开始介绍下内存管理模块了(其实也是梳理下我的知识点)。

        最开始的地方是在head汇编中,如果看boot中那三个汇编的应该记得(那三个汇编还是比较重要的)。首先是分页机制,在CR0的第31位(PG位)置1表示开启分页机制,顺便也介绍下其他几个控制寄存器:CR1保留,没用;CR2 用来记录页面异常时线性地址(不懂没关系,后面会介绍);CR3 当前CPU使用的页目录表的地址(有此可见系统中不仅仅只有一个页目录表,但是在某一时刻有效的页目录表只有一个);当然有关页面操作的前提是CR0的第31位必须打开,也就是必须是在分页机制开启的时候那几个控制寄存器才有效。

        分页机制最最基础的就是把内存空间以4kb为单位分成多个页


系统内存总分布

       在Linux系统中全部内存分布情况为:


        在setup汇编中已经把系统内核代码从0x100000移动到从0地址开始的1MB地址内,再根据main函数中设置内存不超过16MB,所以这里就拿16MB内存为例子;整个内存分布情况为:内核代码及系统数据使用0~1MB ------ 高速缓存使用1~4MB ------ 虚拟内存4MB~xxx(如果有虚拟内存)------ 主内存区xxx~16MB;具体的设置在main函数有,可以自己查看下。如果大于16MB内存那么就会限制只使用低16MB地址内存,大于16MB的内存将会废掉;如果想使用大于16MB,那么就要在main函数中和head汇编中修改下(具体修改就要自己动手了)


分页机制名词介绍

        页目录表:由1024个目录项组成,每个目录项是4个字节组成,目录项中内容为页表结构的起始地址前20位(因为页表结构是2^12,所以低12位可以忽略)和该页表的属性组成;

        页表:页表和页目录表和相似,都是由1024个表项组成,每个表项由4个字节组成,页表项中存放的内容为物理页的起始地址的前20位(因为物理页大小为 2^12,所以低12位可以忽略)和该物理页面的属性组成;

        物理页面:一般是在主内存区中以4kb的倍数为起始地址,大小为4kb的连续内存地址(这里假设没有虚拟内存);

        表项:表项分为页目录表项和页表表项,其中格式都是一样的。前20位页框地址,后12位表示对于页面的属性;表项结构如下:

    

        若是页目录项:页框地址中的前20位表示的是页表的物理起始地址中的前20位(这里有几个重点的:1、表示的是页表的物理地址,而不是线性地址,这两个地址关系后面再分析;2、是起始地址,因为一个页表是4kb大小,所以一个页表就有4kb个地址(一个字节对于一个地址嘛),而起始地址表示偏移量为0的地址;3、前20位,因为分页机制中页(不管是页目录还是页表或者物理页)都是以4kb的倍数为起始地址的,也就是说页的起始地址的低12位全部为0,2^12 = 4kb)

        若是页表项:页框地址中的前20位表示的是物理页的物理起始地址中的前20位;

        低12位则用来表示相应的页的一些属性:p == 是否存在(1 存在;0不存在 == 缺页中断);r/w == 是否可读可写(默认都是可读的,1表示页面可写);u/s == 是否是超级用户(这个到现在还没有怎么用到,1表示超级用户);A == 是否访问,D == 是否修改(这两个位一般由硬件来处理); 

        下面是页目录表、页表、物理页的宏观关系图:



分页机制本质

        在分析分页机制本质之前先要了解几个地址概念:逻辑地址、线性地址、虚拟地址、物理地址;
        首先定义一个地址:0xb8000(这是一个特殊的地址,还记得吗?这是一个显卡映射过来的物理地址,需要在屏幕上显示东西就从这个地址开始往里面写入需要显示的内容和字体属性了);那么现在假设(实模式下) ds = 0xb800  ax = 0x00 ==== ds*16 + ax == 0xb8000 + 0 = 0xb8000
        逻辑地址:就是偏移地址,就上面而言是ax中的地址(不管在保护模式下还是实模式下);
        线性地址:就是段地址加上偏移量(也即是逻辑地址)而形成的32位地址;若未开启分页机制,那么线性地址就和物理地址对应的;开启分页机制,那么线性地址则是有页目录项号(地址的高10位)+ 页表项号(地址的12~21共10位)+ 物理页内偏移量(地址的低12位)共同组成的
        虚拟地址:这个还没仔细研究,每个进程中都有4G的内存地址,但是这不是真实的地址,而是有系统虚拟出来的,所以虚拟地址指定是进程中使用的虚拟地址;
        物理地址:这就是最根本的地址,硬件上的地址,cpu地址总线上使用的地址;
       
        分页机制的本质就是把线性地址转换成物理地址:下面看地址转换图

        根据转换图,分步骤来解释下转换情况:假设线性地址为  0x00c0 f0ef (我承认这个地址是提前设计好的,但仅仅只是为了方便计算,并不影响转换工作)
        1、查找页目录项的物理起始地址:已知线性地址 eax = 0x00c0 f0ef,那么怎么获取页目录项呢?    eax >> 22(将线性地址右移22位)得到页目录项号(一定要记住这是页目录项号要和页目录项起始地址分开;因为线性地址上和页目录有关的就只有前20位地址,所以线性地址中最大可以表示的页面项号为  2^10=1024项;而一个页面有4kb(4096byte),每一个页目录项占用4个字节,所以 4kb/4 = 1kb(1024)项,正好对的上。一般页目录项号都是从0开始的,然后1、2、3、4.....)这里得到的页目录项号为:0x003,化作页目录项的物理起始地址为:0x003 x 4(每一项4个字节) = 0x00c;那么再根据CR3中页目录表的基地址,则可以查找到页目录项的物理起始地址,假设只有一个页目录表,CR0 = 0x000,那页目录项起始物理地址则为:0x00c0 0000; 
        2、根据查找到的页目录项,分析页表物理基地址:从上一步中获取到页目录项,根据目录项结构可以知道,只有前20位是页框地址,后12位是对应页的属性设置;页目录项 & 0xFFFF F000 (其实就是得到前20位页框地址)就可以得到页表物理基地址了;
        3、获取页表项的物理起始地址:和第一步一样,获取到页表项号,通过线性地址 & 0x 3F F000(其实就是提取中间和页表有关的10位地址),将会得到0x0000 F000页表项号(同样和页表物理起始地址区分开),那么页表项物理起始地址为:0x0000 F000 * 4 ;
        4、根据页表项,分析物理页的物理起始地址:步骤同2一样,获取到前20位页框地址,就是物理页的物理起始地址了;
        5、最后把上一步得到的物理页的起始地址加上线性地址中最后12位的页内偏移值,就可以准确的定位到每个字节上了;

        转载请注明作者和原文出处,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/43021405

        若有不正确之处,望大家指正,共同学习!谢谢!!!

        
        
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页