zoukankan      html  css  js  c++  java
  • 自制操作系统(九) 内存管理

    2016.07.04  2016.07.05 2016.07.06

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

            操作系统本质是一个程序,一个大型软件工程(商业化os的情况)。而程序的本质---一方面是所谓的“数据结构+算法”,另一方面则是 封装+抽象。操作系统作为一个程序,一方面是控制硬件启动开机,并且作为第一个在计算机上运行的软件,另一方面,操作系统负责管理计算机的资源(内存管理,文件管理,IO),协助用户要运行的程序在计算机上运行,甚至是多个程序同步运行(进程管理)。所以你可看到,操作系统本质上和那些bs模式的企业管理网站本质没有任何区别,都是管理。只不过操作系统要管理的是计算机资源(在对计算机底层抽象的基础上),和程序,作为一个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; /* 失败 */
    }
  • 相关阅读:
    在 ServiceModel 客户端配置部分中,找不到引用协定
    nopi 导出excel 带图片
    c# async await 理解 结合并行处理
    从数据库更新模型时出现System.ArgumentException
    C# HTTP请求 异步(async await)
    C# HTTP请求
    SQL列转行
    详解C# 匿名对象(匿名类型)、var、动态类型 dynamic
    JavaScript (function (){}()) 与(function(){})()
    记ssh错误排查-ansible
  • 原文地址:https://www.cnblogs.com/rixiang/p/5641936.html
Copyright © 2011-2022 走看看