SmlOS八-内存管理系统实现

  • 内容
  • 评论
  • 相关

内存大小的检测

这里使用了是很笨的方法,就是检测指定内存地址是否可写。

写成c大致伪码大概是:

int count_mem_size()
{
    int i = 0;
    for (int i = 4M ; i < 4G; i+= 0x1000)
    {
        int *p = i;
        *p = 0x11223344;
        if (*p != 0x11223344)
        {
            return i;
        }
    }
    return i;
}

有人可能对这样的代码很好奇,这样写内存,不会内存越界,导致奔溃吗?

第一呢,我们在gdt中设置了可以访问4g的内存段。

第二呢,我们并没有设置相关的idt异常处理函数,所以也不会进入相关的内存操作异常处理函数。

也就是说即使写越界了,也不会收到异常。

所以对于不可访问的内存,所以看到的现象就是,写入内存了一个值,但是读取出来却不一致的情况。

但是这种莫名其妙的操作,很有可能会被C编译器优化掉,所以这部分由汇编实现。

汇编函数中呢,采用了写入再取反再读取的操作,但大致原理是一样的。

内存检测汇编实现:

; 内存检查函数    检查start地址开始到end地址的范围内,能够使用的内存的末尾地址,并将其作为返回值返回
_memtest_sub: ; unsigned int memtest_sub(unsigned int start, unsigned int end)
    PUSH   EDI                  ; 保存EDI ESI EBX
    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

关闭高速缓存

高速缓存简介:

大致就是将内存中经常访问的地址内容存在高速缓存中,在计算机读取内存内容中的时候,先从高速缓存读取,以提升读取速度。

我们需要检测之前关闭高速缓存,以免检测不准确。

//用于设置和清除CR0的NW与CD位,设置高速缓存用 
#define CR0_CACHE_DISABLE  0x60000000  

    //禁止高速缓存
    cr0 = load_cr0(); 
    cr0 |= CR0_CACHE_DISABLE;  
    store_cr0(cr0);           

内存分配管理

我们需要构造一个模块,统一分配和管理内存。

就是malloc函数中,系统底层是如何决定分配哪些内存给你呢。

系统需要做的是:

  • 1.确保分配给你的内存是没有占用的
  • 2.有足够高的效率和内存记录结构体的占用

我们使用的方式是:只记录未使用内存。

比如说系统有100M内存,刚开始只是内核相关占据了1M:

那么内存管理表记录就是:

空闲地址 大小
1M开始 99M

然后我分配了10M内存:(分配空间 1M-11M)

空闲地址 大小
11M开始 89M

然后由分配了5M:(分配空间 11M-16M)

空闲地址 大小
16M开始 84M

然后释放了上面申请的10M:

空闲地址 大小
1M开始 10M
16M开始 84M

这里的空闲内存条目变成了两条。

下次分配会找第一个满足条件的内存块。

这样的好处是,可以用很少的内存管理相关内存,记录我们需要的信息。

然后我们需要维护一个内存管理表。
内存管理表的结构如下:

struct MEMMAN
{
   int frees, maxfrees, lostsize, losts;
   struct FREEINFO free[MEMMAN_FREES];
};

内存管理结构

  • frees:内存可用信息条目的数目
  • maxfrees: frees的最大值
  • lostsize: 释放失败的内存大小的总和
  • losts: 释放失败的次数
  • struct FREEINFO free[MEMMAN_FREES];

内存可用信息条目数组

单块内存信息结构体:

内存可用信息条目

struct FREEINFO
{
   unsigned int addr, size;
};
  • addr: 可用内存块首地址
  • size: 可用内存块大小

内存分配

参考上面的例子说明。

内存分配算法简要来说就是,在内存分配函数中,首先需要在管理表中遍历,找到第一个可用内存,然后再 内存中割除这一部分。

向上取整

主要目的是为了减少内存空洞的可能性。

当然如果申请的内存过于零碎,重复的释放和申请,可能会超过内存管理表空闲块最大记录数,所以分配的时候一般会以4K向上取整。

4K向上取整的意思就是,无论需要分配内存的长度,最低都会分配4K,如果超过4k,也会分配4K的倍数。

比如你申请1byte,会分配给你4k大小。
你申请4k + 1byte,会分配给你8k大小。

核心计算语句:

//向上以4K取整
size = (size + 0xfff) & 0xfffff000; 

内存释放

内存的释放主要对内存管理表进行操作,其中需要注意是,

释放的内存是否可以和表中空闲段段合并成一段:

  • 1.可以合并的话,尽量合并

  • 2.不能合并,分拆出一个空闲记录条目,加入内存管理表