《30天自制操作系统》学习笔记——第9天 内存管理

先啰嗦几句
整整一年之后开始”第9天”的学习,这次的时候,和一年前的感觉区别很大,一年前大量的力气花在理解作者对一些理论的阐述,问题的解释,现在理解起来则容易的多,更多的是再验证一年来学的《计算机组成》,《操作系统》的知识;不变的是对这本书很好的评价,一年前觉得这本书是图文并茂,讲解清晰,讲其然,讲其所以然;一年后,突出感觉这是一本讲究实践的书,作者在书中展现了制作一个操作系统的思路,每一步是如何考虑的,而且把很多优秀的实践融合在里面,隐含很多思想,比如封装,模块化,数据结构等等,对于初学者是一种润物无声的影响,简直是打通“任督二脉”。这次读和之前方式不太一样,之前是,看懂之后,代码要自己尝试在敲一遍,这一次只读不写代码,而且我也打算把剩下的20章挑出几章来看,一是时间紧张有更多事情要做,另一方面这个系统虽然五脏俱全,但毕竟是一个“toy os“,距离unix,linux这些系统毕竟差别太多,而且作者花费时间较多的图形界面的工作也并不是我最关心的。在读这本书有”善始善终“的想法,另一方面也是为进一步的学习打个基础。


正文部分

内存管理讲的两部分,容量检查对内存的管理,作者的讲解已经非常清晰,所以这里不会重复,我会记录我个人对一些细节的理解,可能会有些和内存管理的题目跑偏

容量检查

作者不想调用各种各样的BIOS函数,所以自己写程序检查。容量检查的做法是,从内存起始位置开始,写到地址一个数据,再读回来,两次一样内存就是正常的,地址递增,直到出现不一致,说明内存地址结束,从而确定大小(开机自检时,内存错误已经被检查)

去除Cache的干扰

Cache是用于解决CPU和内存速度不匹配的问题,是一种高速存储器,它保存的是内存内容的副本。基于局部性原理,系统会把将要访问的内存内容装到cache里,CPU下一次访问内存时直接读取cache内容,提高速度,同样如果CPU需要多次修改同一内存地址处的内容,也是先修改cache的内容,最后在写回内存(当然需要一个内存与Cache的映射机制来支持这一做法)。所以如果不禁用Cache的话,对于上文提到的内存检查的方法是有干扰的。
示意图
涉及到关闭Cache,C程序就无能为力了,所以需要C+汇编混合编程

C和汇编混合,在nask编译器里,要注意的就是汇编代码的函数要加GLOBAL声明,而且lable前面要加’_’才能很好的和C中的函数链接,Makefile也要把汇编编译的obj和C编译的obj链接起来,看第3天里面的一个例子

naskfunc.nas

; naskfunc
; TAB = 4

[FORMAT "WCOFF"]        ; 制作目标文件的模式
[BITS 32]               ; 制作32位模式使用的机械语言


; 制作目标文件的信息

[FILE "naskfunc.nas"]   ; 源文件名信息

GLOBAL  _io_hlt         ; 程序中包含的函数名


; 以下是实际的函数

[SECTION .text]         ;目标文件中写了这些之后在写程序 

_io_hlt :               ; void io_hlt(void);
        HLT
        RET

bootpak.c

/*告诉C编译器,有一个函数在别的文件里*/
void io_hlt(void);

void HariMain(void)
{

fin:
    io_hlt(); /*执行naskfunc.nas里的io_hlt*/
    goto fin;
}

Makefile中的有关部分


MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.exe
CC1      = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM  = $(TOOLPATH)obj2bim.exe

bootpack.gas : bootpack.c Makefile
    $(CC1) -o bootpack.gas bootpack.c

bootpack.nas : bootpack.gas Makefile
    $(GAS2NASK) bootpack.gas bootpack.nas

bootpack.obj : bootpack.nas Makefile
    $(NASK) bootpack.nas bootpack.obj bootpack.lst

naskfunc.obj : naskfunc.nas Makefile
    $(NASK) naskfunc.nas naskfunc.obj naskfunc.lst

bootpack.bim : bootpack.obj naskfunc.obj Makefile
    $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
        bootpack.obj naskfunc.obj

内存检查代码

486以上的CPU才有缓存,所以要先检查是386还是486,可以看到代码以有一个很”反复“的检查过程,可能是为了确保吧

#define EFLAGS_AC_BIT       0x00040000
#define CR0_CACHE_DISABLE   0x60000000

unsigned int memtest(unsigned int start, unsigned int end)
{
    char flg486 = 0;
    unsigned int eflg, cr0, i;

    /* 确认CPU是386的还是486以上*/
    eflg = io_load_eflags();
    eflg |= EFLAGS_AC_BIT; /* AC-bit = 1 */
    io_store_eflags(eflg);
    eflg = io_load_eflags();
    if ((eflg & EFLAGS_AC_BIT) != 0) { /*如果是386,即使设定AC=1,AC的值还是会自动回到0*/
        flg486 = 1;
    }
    eflg &= ~EFLAGS_AC_BIT; /* AC-bit = 0 */
    io_store_eflags(eflg);

    if (flg486 != 0) {
        cr0 = load_cr0();
        cr0 |= CR0_CACHE_DISABLE; /*禁止Cache*/
        store_cr0(cr0);
    }

    i = memtest_sub(start, end);

    if (flg486 != 0) {
        cr0 = load_cr0();
        cr0 &= ~CR0_CACHE_DISABLE; /*允许缓存*/
        store_cr0(cr0);
    }

    return i;
}

还有一个地方值得注意,在内存检查完成之后,要把禁用cache关掉回去,这种类似于”申请—使用—释放“的形式还有很多,比如指针的使用,使用winsock时使用WSACleanup()释放socket等等。

编译器优化造成的bug

作者这个bug虽然一句”终于搞清楚了原因“轻描淡写的带过了debug的过程,相必他在调试的时候一定相当蛋疼,不过他给出了一种调试的方法,查看C语言汇编之后的汇编代码,查看程序到底执行了哪些指令,作者调bug竟然都想到了这样的调试方法,深表同情 ; (
下面把C程序,汇编之后的代码,以及用汇编重写之后的代码贴在这里
C

unsigned int memtest_sub(unsigned int start, unsigned int end)
{
    unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
    for (i = start; i <= end; i += 0x1000) {
        p = (unsigned int *) (i + 0xffc);
        old = *p;           /*先记住修改前的值*/
        *p = pat0;          /*试写*/
        *p ^= 0xffffffff;   /*反转*/
        if (*p != pat1) {   /*检查反转结果*/
not_memory:
            *p = old;
            break;
        }
        *p ^= 0xffffffff;   /*再次反转*/
        if (*p != pat0) {   /*检查值是否恢复*/
            goto not_memory;
        }
        *p = old;           /*恢复为修改之前的值*/
    }
    return i;
}

相应的汇编

_memset_sub:
    PUSH    EBP                 ;C编译器的固定语句
    MOV EBP,ESP;
    MOV EDX,DWORD [12 + EBP]    ;EDX = end
    MOV EAX,DWORD [8 + EBP]     ;EAX = start;/*EAX 是 i*/
    CMP EAX,EDX;                ;if(EAX > EDX) goto L30;
    JA L30
L36:
L34:
    ADD EAX,4096                ;EAX += 0x1000
    CMP EAX,EDX                 ;if(EAX <= EDX) goto L36;
    JBE L36
L30:
    POP EBP                     ;接受前文中push的EBP
    RET                         ;   return;

使用汇编重写之后

_memtest_sub:   ; unsigned int memtest_sub(unsigned int start, unsigned int end)
        PUSH    EDI                     ; 由于还要使用EBX, ESI, EDI
        PUSH    ESI
        PUSH    EBX
        MOV     ESI,0xaa55aa55          ; pat0 = 0xaa55aa55;
        MOV     EDI,0x55aa55aa          ; pat1 = 0x55aa55aa;
        MOV     EAX,[ESP+12+4]          ; i = start;
mts_loop:
        MOV     EBX,EAX
        ADD     EBX,0xffc               ; p = i + 0xffc;
        MOV     EDX,[EBX]               ; old = *p;
        MOV     [EBX],ESI               ; *p = pat0;
        XOR     DWORD [EBX],0xffffffff  ; *p ^= 0xffffffff;
        CMP     EDI,[EBX]               ; if (*p != pat1) goto fin;
        JNE     mts_fin
        XOR     DWORD [EBX],0xffffffff  ; *p ^= 0xffffffff;
        CMP     ESI,[EBX]               ; if (*p != pat0) goto fin;
        JNE     mts_fin
        MOV     [EBX],EDX               ; *p = old;
        ADD     EAX,0x1000              ; i += 0x1000;
        CMP     EAX,[ESP+12+8]          ; if (i <= end) goto mts_loop;
        JBE     mts_loop
        POP     EBX
        POP     ESI
        POP     EDI
        RET
mts_fin:
        MOV     [EBX],EDX               ; *p = old;
        POP     EBX
        POP     ESI
        POP     EDI
        RET

内存容量检查部分到此为止

内存管理

作者使用的是一种分区管理的方式,以0x1000字节为单位进行管理,作者的代码,分析都非常清晰。
作者特别提到了一个向上取整的技巧,记录在此
以0x1000为单位向上取整数(只对2进制数有效)

i = (i + 0xfff ) & 0xfffff000;  //2进制数的向上舍入
i = i - (i % 1000);             //10进制数的向下舍入

说明
本文由giantpoplar发表于CSDN
地址
http://blog.csdn.net/giantpoplar/article/details/48227299
转载请保留本说明

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值