自制操作系统 内存管理

Posted NONE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自制操作系统 内存管理相关的知识,希望对你有一定的参考价值。

2016.07.04  2016.07.05 2016.07.06

参考书籍:《30天自制操作系统》、《自己动手写操作系统》

 

        操作系统本质是一个程序,一个大型软件工程(商业化os的情况)。而程序的本质---一方面是所谓的“数据结构+算法”,另一方面则是 封装+抽象。操作系统作为一个程序,一方面是控制硬件启动开机,并且作为第一个在计算机上运行的软件,另一方面,操作系统负责管理计算机的资源(内存管理,文件管理,I\\O),协助用户要运行的程序在计算机上运行,甚至是多个程序同步运行(进程管理)。所以你可看到,操作系统本质上和那些b\\s模式的企业管理网站本质没有任何区别,都是管理。只不过操作系统要管理的是计算机资源(在对计算机底层抽象的基础上),和程序,作为一个boss级别的程序管理程序(这里你要理解程序运行所需的那些基础,内存、寄存器、cpu等等……了解了这些基础才能知道操作系统作为一个boss程序怎么管理应用程序)。

      所以你可以看到,计算机的目的就是要运行程序。而操作系统是一个在计算机上运行的程序,目的是帮助计算机在人类的操作下更好的运行程序。

      这一章的内存管理便是操作系统作为boss程序的一种体现。操作系统管理内存是因为,计算机要运行程序需要内存,所以操作系统作为程序的大boss,来负责管理并给应用程序(小弟们)分配、回收内存。

 

      首先,内存管理要涉及缓存的概念。在我们做内存检查时要将缓存设为OFF。

       高速缓存位于主存与CPU之间,作为一种缓冲,因为CPU太快,主存存取速度太慢。高速缓存则是一种存取速度接近CPU的存储器。其工作方式就在于:访问内存前,先将要访问的地址和内容存入高速缓存里。往内存里写入数据时也一样。首先更新高速缓存的信息,再写入内存。

      更细致地讲,高速缓存的内存空间与内存是存在一定映射规则的:1.全相连映射  2.直接相连映射  3.组相连映射    具体不多谈。因为本篇并不涉及那么深的内容。

      本章需要的是内存管理,最基本的是内存检查。要检查内存,需要往内存里随便写入一个值,然后读取,来检查读取的值与写入的值是否相等。若相等(说明这里是可用的内存),这时需要先关闭缓存,否则会写在缓存里,而不是内存。

       下面C函数的目标是关闭高速缓存:

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; /* 禁止缓存 */
        store_cr0(cr0);
    }

    i = memtest_sub(start, end);

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

    return i;
}

上面C语言需要调用下面两个汇编函数,因为为了禁止缓存,需要对CR0寄存器的某一标志位进行操作:

_load_cr0:        ; int load_cr0(void);
        MOV        EAX,CR0
        RET

_store_cr0:        ; void store_cr0(int cr0);
        MOV        EAX,[ESP+4]
        MOV        CR0,EAX
        RET

 

至于内存检查部分,用的是汇编语言函数:

_memtest_sub:    ; unsigned int memtest_sub(unsigned int start, unsigned int end)
        PUSH       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

     上述代码很好懂,from start to end循环向内存写入0xaa55aa55(在那之前先将内存原本值存入edx寄存器),再使得该内存地址与0xffffffff做XOR异或操作。若此时该内存里值为0x55aa55aa,则该内存有效。将edx寄存器里的值放回去就好。

      以上便是内存检查的部分的函数,在内存检查的基础上,内存管理要做的两件事一是内存分配,一是内存释放。例如现在要启动一个应用程序,需要nKB内存,其需要的内存便由操作系统来分配。应用程序结束后再收回。

 

关于内存管理,很多前辈大神设计过很多牛b的算法~当然那是要应用于大型商业操作系统的情况,我们的sonnos只是个小demo,所以只需要一个简单的方法就好。

那就是列表管理法:

      把类似“从xxx号地址开始的yyy字节的空间是空着的”这种信息列在表里。

struct FREEINFO {    /* 可用内存信息 */
    unsigned int addr, size;
};

struct MEMMAN {        /* 内存管理 */
    int frees, maxfrees, lostsize, losts;
    struct FREEINFO free[MEMMAN_FREES];
};

      如果用java描述,大概是这样的结构:

class  
{
    private List<FREEINFO> freeInfos;
    private int frees;
    private int maxfrees;
    private int lostsize;
    private int losts;
}

      可以看出FREEINFO是这个内存List的单元,该单元,用addr和size标志地址和大小。

基于这样的数据结构,写出的内存分配程序:

void memman_init(struct MEMMAN *man)
{
    man->frees = 0;            /* 可用信息数目*/
    man->maxfrees = 0;        /* 用于观察可用状况:frees的最大值 */
    man->lostsize = 0;        /* 释放失败的内存大小总和 */
    man->losts = 0;            /* 释放失败次数 */
    return;
}

unsigned int memman_total(struct MEMMAN *man)
/* 报告空余内存大小的合计 */
{
    unsigned int i, t = 0;
    for (i = 0; i < man->frees; i++) {
        t += man->free[i].size;
    }
    return t;
}

unsigned int memman_alloc(struct MEMMAN *man, unsigned int size)
/* 分配 */
{
    unsigned int i, a;
    for (i = 0; i < man->frees; i++) {
        if (man->free[i].size >= size) {
            /* 找到了足够大的内存 */
            a = man->free[i].addr;
            man->free[i].addr += size;
            man->free[i].size -= size;
            if (man->free[i].size == 0) {
                /* 如果free[i]变成了0,就减掉一条可用信息 */
                man->frees--;
                for (; i < man->frees; i++) {
                    man->free[i] = man->free[i + 1]; /* 代入结构体 */
                }
            }
            return a;
        }
    }
    return 0; /* 没有可用空间 */
}

 

释放内存函数:

int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)
/* 释放 */
{
    int i, j;
    /* 为便于归纳内存,将free[]按照addr的顺序排列*/
    /* 所以,先决定应该放在哪里 */
    for (i = 0; i < man->frees; i++) {
        if (man->free[i].addr > addr) {
            break;
        }
    }
    /* free[i - 1].addr < addr < free[i].addr */
    if (i > 0) {
        /* 前面有可用内存 */
        if (man->free[i - 1].addr + man->free[i - 1].size == addr) {
            /* 可以与前面的可用内存归纳到一起 */
            man->free[i - 1].size += size;
            if (i < man->frees) {
                /* 后面也有 */
                if (addr + size == man->free[i].addr) {
                    /* 也可以与后面的可用内存归纳到一起 */
                    man->free[i - 1].size += man->free[i].size;
                    /* man->free[i]删除 */
                    /* free[i]变成0后归纳到前面去 */
                    man->frees--;
                    for (; i < man->frees; i++) {
                        man->free[i] = man->free[i + 1]; /* 结构体赋值 */
                    }
                }
            }
            return 0; /* 成功完成 */
        }
    }
    /* 不能与前面的可用空间归纳到一起*/
    if (i < man->frees) {
        /* 后面还有 */
        if (addr + size == man->free[i].addr) {
            /* 可以与后面的内容归纳到一起 */
            man->free[i].addr = addr;
            man->free[i].size += size;
            return 0; /* 成功完成 */
        }
    }
    /* 既不能与前面的归纳到一起,也不能与后面的归纳到一起 */
    if (man->frees < MEMMAN_FREES) {
        /* free[i]之后的,向前移动,腾出一点可用空间 */
        for (j = man->frees; j > i; j--) {
            man->free[j] = man->free[j - 1];
        }
        man->frees++;
        if (man->maxfrees < man->frees) {
            man->maxfrees = man->frees; /* 更新最大值 */
        }
        man->free[i].addr = addr;
        man->free[i].size = size;
        return 0; /* 成功完成*/
    }
    /* 不能往后移动 */
    man->losts++;
    man->lostsize += size;
    return -1; /* 失败 */
}

 

以上是关于自制操作系统 内存管理的主要内容,如果未能解决你的问题,请参考以下文章

自制操作系统05开启内存分页机制

自制操作系统03读取硬盘中的数据

自制操作系统11中场休息之细节是魔鬼

《30天自制操作系统》第二天

自制操作系统06终于开始用 C 语言了,第一行内核代码!

6_30天自制操作系统第6天心得体会