您的位置:首页 > 其它

《30天自制操作系统》笔记(07)——内存管理

2014-06-04 17:14 337 查看
[b]《30天自制操作系统》笔记(07)——内存管理 [/b]

进度回顾

上一篇中处理掉了绝大部分与CPU配置相关的东西。本篇介绍内存管理的思路和算法。

现在想想,从软件工程师的角度看,CPU也只是一个软件而已:它的功能就是加载指令、执行指令和响应中断,而响应中断也是在加载指令、执行指令。就像火车沿着一条环形铁轨前进;当中断发生时,就好像铁轨岔口处变轨了,火车就顺着另一条轨迹走了;走完之后又绕回来重新开始。决定CPU是否变轨的,就是CPU里的特定寄存器。

_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


汇编版本的memtest_sub
知道了内存容量,就可以进行管理了。

关闭CPU高速缓存

486以上的CPU是有高速缓存(cache)的。CPU每次访问内存,都要将所访问的地址和内容存入cache,也就是存放成这样"18号地址的值是54"。如果下次要用18号地址的内容,CPU就不需要再读内存了(速度慢),而是直接从cache中取得18号地址的内容(速度快)。

如果开启着CPU高速缓存(cache),上述的检测代码就不会正常工作。因为写入一个内存地址,然后立即读出,这样的操作符合cache到的情况,CPU不会从内存读,而是直接读cache到的东西。结果,所有的内存都"正常",检测代码就起不到作用了。

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

i = memtest_sub(start, end);

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

return i;
}


内存管理算法

假设内存有128MB(0x08000000字节),以4KB(0x1000字节)为单位进行管理。

最简单的方法

128MB/4KB=32768。所以我们创建32768字节的区域,写入0表示对应的内存是空闲的,写入1表示对应的内存是正在使用的。这个方法的名字我没有查到。

char a[32768];
for (i = 0; i < 1024; i++) {
a[i] = 1; //一直到4MB为止,标记为正在使用(BIOS、OS等)
}
for (i = 1024; i< 32768; i++) {
a[i] = 0; //剩下的全部标记为空闲
}


比如需要100KB的内存,那么只要从a中找出连续25个标记为0的地方就可以了。

//从a[j]到a[j + 24]为止,标记连续为0";
j = 0;
再来一次:
for (i = 0; i < 25; i++) {
if (a[j + i] != 0) {
j++;
if (j < 32768 - 25) goto 再来一次;
"没有可用内存了";
}
}
//从j * 0x1000开始的100KB空间得到分配
for (i = 0; i < 25; i++) {
a[j + i] = 1;
}


需要收回这100KB的时候,用地址值/0x1000,计算出j就可以了。

j = 0x00123000 / 0x1000;
for (i = 0; i < 25; i++) {
a[j + i] = 0;
}


当然,我们可以用1bit来代替1个char,这样所需的管理空间就可以省下7/8,使用方法则是一样的。

列表管理

把类似于"从xxx号地址开始的yyy字节的空间是空闲的"这种信息都存储到列表里。

struct FREEINFO {    /* 可用状况 */
unsigned int addr, size;
};

struct MEMMAN {        /* 内存管理 */
int frees, maxfrees, lostsize, losts;
struct FREEINFO free[MEMMAN_FREES];
};
struct MEMMAN memman;
memman.frees = 1;//可用状况list中只有1件
memman.free[0].addr = 0x00400000;//从0x00400000号地址开始
memman.free[0].size = 0x07c00000;//有124MB可用


比如,如果需要100KB的内存,只要查看memman中free的状况,从中找到100MB以上的可用空间就行了。

for (i = 0; i < memman.frees; i++) {
if (memman.free[i].size >= 100 * 1024) {
//找到可用空间
memman.free[i].addr += 100 * 1024;
memman.free[i].size -= 100 * 1024;
}
}


释放内存时,增加1条可用信息,frees加1。而且还要看看新释放出的内存与相邻的内存能不能连到一起,如果可以,就要归为1条。

与上文的最简单的方法相比,这种列表管理的方法,占用内存少,且内存的申请和释放更迅速。

缺点是释放内存的代码比较复杂。另外,如果内存变成零零碎碎的,那么需要的MEMMAN里的数组就会超过1000,这是个问题。如果真发生这种情况,只能将一部分零碎的空闲内存都视作被占用的,然后合并。

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; /* 失败 */
}


总结

内存管理要结合GDT的设定进行。按段(Segment)设计的GDT,内存就得按段申请和回收。按页设计的GDT,额我不知道,以后再说。

内存管理需要的预备知识还包括"获取内存容量"、"禁止/启用高速缓存"、"数据结构-链表"。

内存管理的算法还有很多,本篇只给出了两种最基本最简单的,够做个简易的OS就行了,现在不是深究算法的时候。

请查看下一篇《《30天自制操作系统》笔记(08)——叠加窗口刷新》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: