搞定了鼠标和键盘这两个外设的处理,终于走到了内存管理这一步。平时写上层应用的时候,就是malloc、free、memset这几件套,较少关注内存多少、内存能不能用等底层细节,但是要制作操作系统,对于内存的检查和管理就不可或缺了,所以一章的内容是比较重要的。

这一章着重介绍了内存的检查和内存容量的管理。

一 内存检查

考虑在操作系统上电初始化的时候做一次内存检查,内存检查的代码实现如下:

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;
}

内存检查memtest步骤如下:

【1】确认CPU是386还是486以上的

如果是486以上,EFLAGS寄存器的第18位应该是所谓的AC标志位。这里,我们有意识地把1写入到这一位,然后再读出EFLAGS的值,继而检查AC标志位是否仍为1,最后,将AC标志位重置为0。

【2】禁止缓存

为了避免CPU内部的高速缓存(cache memory)对内存管理的影响,这边在进行内存检查之前需要先禁止内部高速缓存,检查完毕之后再开启。

实现的方法也很简单,就是对CR0控制寄存器的第29位NW和第30位CD置位,即可以禁止(具体可以参考我关于寄存器的帖子【操作系统】CPU寄存器详解)。

【3】内存检查memtest_sub

内存检查的思路是这样的,给一段内存赋值0xaa55aa55,尝试反转之后对比是不是变成了0x55aa55aa,然后再反转对比是不是变回0xaa55aa55,如果两次反转结果都正确,那么则认为内存正常无误。

作者一开始使用的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;
}

通过查看编译出来的汇编语言,发现GCC在编译这一段C语言是,认为这一段操作反转来反转去还不是原值,所以在编译的过程中优化掉了。所以这一段内存检查的操作,还是需要使用底层的汇编直接实现:

_memtest_sub:    ; unsigned int memtest_sub(unsigned int start, unsigned int end)PUSH    EDIPUSH ESIPUSH EBXMOV      ESI,0xaa55aa55MOV       EDI,0x55aa55aaMOV       EAX,[ESP+12+4]        ; i = start
mts_loop:MOV        EBX,EAXADD      EBX,0xffc       MOV     EDX,[EBX]MOV        [EBX],ESIXOR        DWORD [EBX],0xffffffffCMP       EDI,[EBX]JNE        mts_finXOR      DWORD [EBX],0xffffffffCMP       ESI,[EBX]JNE        mts_finMOV      [EBX],EDXADD        EAX,0x1000CMP       EAX,[ESP+12+8]JBE     mts_loopPOP     EBXPOP      ESIPOP      EDIRET
mts_fin:MOV     [EBX],EDXPOP        EBXPOP      ESIPOP      EDIRET

【4】允许缓存

禁止缓存的逆操作。

最终打印出来结果如下,检查了系统32M的内存:

二 内存管理

内存管理作者这边提到了几种方法:

【1】管理表:比如将一段125M的内存,按4K每块的形式管理,则一共分为32K块。建立一个32K的数组,用数组元素的0/1来代表每块内存是否使用。(优点:简单易理解;缺点:随着总内存的增大,管理表的大小也随之增大)

【2】改进管理表(用位的形式来存储):对上面的管理表进行优化,因为表示内存使用情况只需要0/1,用比特位来代替字节的表示,可以将内存管理表缩小到原来的1/8。(优点:简单易理解,相比于管理表,占用空间有所缩减;缺点:编程麻烦一些,而且相对于下面第三种方法,占用的空间还是较大)

【3】列表标签的形式管理:结构体记录使用标签,把类似于“从xxx号地址开始的yyy字节的空间是空着的”这种信息存在列表里,并根据实际使用情况进行更新。(优点:占用内存少,存取速度快,前两种都需要循环查表的操作,这个方法不用;缺点:管理程序变的复杂了,而且如果内存使用比较零散时,记录也会相应地增加)

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

为了简化开发,我们基于第三种方法,采用了一种“暂时割舍小块内存”的优化方法,只有当memman有空余的时候,才会对割舍掉内存进行检查找回。

申请内存memman_alloc如下(该函数很简单,不再赘述):

#define MEMMAN_FREES     4090    // 大约是 32 KB
#define MEMMAN_ADDR         0x003c0000struct FREEINFO {    /* 可用信息 */unsigned int addr, size;
};struct MEMMAN {      /* 内存管理 */int frees, maxfrees, lostsize, losts;struct FREEINFO free[MEMMAN_FREES];
};void memman_init(struct MEMMAN *man){man->frees = 0;            /* 可用信息数目 */man->maxfrees = 0;         /* 可用于观察可用状况:frees的最大值 */man->losts = 0;            /* 释放失败的内存大小总和 */man->lostsize = 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;
}/*** @return start address of the mem ; 0 if can't alloc*/
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) {man->frees--;// del 1 INFOfor (; i < man->frees; i++) {// 移位处理,余量充足man->free[i] = man->free[i + 1];}}return a;}}return 0;
}

释放内存memman_free如下:

/*** @brief 向 addr 处分配 size 大小的内存空间,考虑到前后内存的合并* * @param man * @param addr * @param size * @return 0 if successful ; -1 if fails*/
int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size) {int i, j;/* 假装 free[] addr 是按照顺序排列的 */// 寻找地址// free[i - 1].addr < addr < free[i].addr for (i = 0; i < man->frees; i++) {if (man->free[i].addr > addr) {break;}}/* 前面有可用内存 */if (i > 0) {if (man->free[i - 1].addr + man->free[i - 1].size == addr) {/* 【1】与前面的内存合并 */man->free[i - 1].size += size;/* 【2】后面有可用内存 */if (i < man->frees) {if (addr + size == man->free[i].addr) {// 与后面的合并man->free[i - 1].size += man->free[i].size;man->frees--;for (; i < man->frees; i++) {man->free[i] = man->free[i + 1];}}}return 0;}}/* 【3】后面有可用内存 */if (i < man->frees) {if (addr + size == man->free[i].addr) {// 与后面的合并man->free[i].addr = addr;man->free[i].size += size;return 0;}}/* 【4】既不能与前面归纳到一起,也不能与后面归纳到一起 */if (man->frees < MEMMAN_FREES) {// free[i] 之后的向后挪for (j = man->frees; j > i; j--) {man->free[j] = man->free[j - 1];}man->free[i].addr = addr;man->free[i].size = size;man->frees++;if (man->maxfrees > man->frees) {// 更新最大值man->maxfrees = man->frees;}return 0;}man->losts++;man->lostsize += size;return -1;
}

这个memman_free略显复杂,这边做一个详细的讲解,上面代码注释中的【1-4】分别代表了内存释放的几种情况:

【1】与前面内存合并的情况:

【2】前后相接的情况:

【3】与后面内存合并的情况:

【4】与前后均不相接的情况:

除了上面四种情形之外的其他情形,诸如重复free同一块内存(double free)、free越界等情况,均视为free失败,报错退出。

综上,在主函数中的实际操作如下:

#define MEMMAN_ADDR          0x003c0000
void HariMain(void)
{//(中略)unsigned int memtotal;struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;//(中略)memtotal = memtest(0x00400000, 0xbfffffff);memman_init(memman);memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */memman_free(memman, 0x00400000, memtotal - 0x00400000);//(中略)sprintf(s, "memory %dMB free : %dKB",memtotal / (1024 * 1024), memman_total(memman) / 1024);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
}

运行结果如下:

【操作系统】30天自制操作系统--(8)内存管理相关推荐

  1. 写在《30天自制操作系统》上市之前

       这本<30天自制操作系统>马上就要在各大书店和网上商城全面上架了,作为本书的4位译者之一,我负责翻译了本书约三分之二的内容.这是我参与翻译的第一本译著,我感到很激动也很紧张,因为我知 ...

  2. 《30天自制操作系统》笔记(04)——显示器256色

    <30天自制操作系统>笔记(04)--显示器256色 进度回顾 从最开始的(01)篇到上一篇为止,已经解决了开发环境问题和OS项目的顶层设计问题. 本篇做一个小练习:设置显卡显示256色. ...

  3. 《30天自制操作系统》学习笔记--第好多天

    之前看<30天自制操作系统>,参考而成,和书中系统并不完全一致,是在原有基础上按照自己的习惯而成,由于水平和工作原因,未完成内存管理和文件系统,有兴趣者可以通过以下网址https://gi ...

  4. 由《30天自制操作系统》引发的漫画创作

    大家可还记得<30天自制操作系统>的封面上的那只猫吗?记得当时,在果壳网有人问,为何这只猫长了两只尾巴呢,延着这条线,我把这本书捧上了展示的舞台.事隔四个多月,我又重提此书. 这本经我手宣 ...

  5. 索骥馆-DIY操作系统之《30天自制操作系统》扫描版[PDF]

    内容简介: <30天自制操作系统>是一本兼具趣味性.实用性与学习性的操作系统图书.作者从计算机的构造.汇编语言.C语言开始解说,让读者在实践中掌握算法.在这本书的指导下,从零编写所有代码, ...

  6. 30天自制操作系统——第五天

    第五天 参考<30天自制操作系统>GDT&IDT - 谷月轩 - 博客 梳理一下文件 现在我们拥有这么9个文件: ipl10.nas InitialProgramLoader, 占 ...

  7. 【操作系统】30天自制操作系统--(9)叠加处理

    这一章主要是处理之前遇到的图层叠加的问题.[操作系统]30天自制操作系统--(7)鼠标移动与32位切换 一 内存管理优化 上一章的内存管理虽然写好,但是还是有不完善的地方.因为如果不对申请内存的大小有 ...

  8. 《30天自制操作系统》第9天

    第九天 内存管理 1.整理源文件 这一节只是进行了代码整理,把鼠标键盘相关的内容转移到了特定的文件里. 2.内存容量检查(1) 要做内存管理,首先得知道内存的容量,怎么知道内存的容量呢?BIOS可以告 ...

  9. 【操作系统】30天自制操作系统--(18)应用程序

    本章主要介绍了文件处理的相关操作以及尝试制作第一个应用程序hlt. 一 type命令 与Linux里面的type命令不同,Windows命令行中的type命令是用来查看文件内容的.在自制的操作系统中, ...

  10. 30天自制操作系统第五天

    操作系统实验日志5 第5天:结构体.文字显示与GDT/IDT初始化 30天自制操作系统第五天 操作系统实验日志5 一.实验主要内容 1. 内容1:接收启动信息 2. 内容2:使用结构体 3. 内容3: ...

最新文章

  1. servlet 访问项目
  2. 知乎高赞:985计算机视觉毕业后找不到工作怎么办?怒刷leetcode,还是另寻他路?
  3. 理解并使用ASP.NET的高级配置
  4. HDU - 1754 I Hate It(Splay-区间最大值)
  5. 常用liunx / mysql命令
  6. 拦截mysql执行计划数据_MySQL执行计划详解
  7. 账龄分析表excel模板_优秀财务的Excel水平!
  8. 【前端】vue阶段案例:组件化-房源展示
  9. 面试时我们应该问面试官什么问题?
  10. tensorflow笔记-文本情感分类
  11. java结束程序是什么语句,在Java程序中,每条功能执行语句的最后都必须用结束。...
  12. 中国境内哪个chatGPT最好用
  13. Alarm机制-学习记录
  14. r语言remarkdown展示图_十个超级好用的R语言编程技巧,一般人绝不知道!
  15. 数据类型与堆栈内存练习数据类型检测
  16. 解决multiple ‘X-Frame-Options‘ headers with conflicting values (‘DENY, SAMEORIGIN‘)
  17. 计算机修改users用户名,笔记本电脑更改用户名_笔记本电脑更改user
  18. android 文件浏览器源码,android 文件管理器源码
  19. 科目三必看要点 驾驶经验汇总
  20. Qt5.13.2中配置opencv4.5.0踩坑记录

热门文章

  1. Springboot养老院管理系统
  2. python如何爬取动态网页数据
  3. 引导滤波算法理论及公式的推导
  4. 编辑距离算法(LD)详解
  5. aviator使用手册在线
  6. 高中网络技术应用计算机病毒教案,信息安全和保护高中信息技术教案
  7. 计算机网络实验(思科)
  8. jabref java_一个开源的参考文献管理软件:JabRef
  9. 西门子、施耐德、三菱、RA:全球主要工控协议及端口解析
  10. matlab机器人雅可比矩阵实验,机械臂通过雅可比矩阵实现正运动学及逆运动学迭代解(工具:matlab)...