《30天自制操作系统》09_day_学习笔记

harib06a:
  在昨天的最后一部分,我们已经变成了32位的模式,目的就是希望能够使用电脑的全部内存。
  虽然鼠标的显示处理有一些叠加问题,不过笔者为了不让我们感到腻烦,先带我们折腾一下内存
  这里笔者有把bootpack.c文件做了整理:

  

  我们可以看到,把不同的函数有封装到了不同的源文件中。
harib06b:
  折腾了这么长时间的鼠标,大家都累了!我们来折腾一下内存吧
  笔者首先给我们科普了一点先验知识:
    1、CPU每次访问内存都要将访问的地址和内容写入到CATCH中(写数据也是一样的)
    2、观察机器语言的流程会发现,9成以上的时间浪费在循环上面了。
    3、386及以下版本的CPU没有缓存,486及以上版本的CPU都有缓存。
  内存检查思路:内存检查时,往内存中随便写一个值,然后马上读取,检查读取的值与写入的值是否相等;如果内存连接正常,则写入的值能够记在内存中。

  (CPU中都有缓存,所以要先将缓存设为OFF再进行上述操作)

//根据上面的思路,笔者编写了内存检查函数memtest:
unsigned int memtest(unsigned int start, unsigned int end){
  char flg486 = 0;
  unsigned int eflg, cr0, i;
  /* 确认CPU的版本, */
  eflg = io_load_eflags();
  eflg |= EFLAGS_AC_BIT;                  /* AC-bit = 1 */
  io_store_eflags(eflg);                  //对EFLAGS寄存器进行处理,检查CPU是386的还是486以上的。
  eflg = io_load_eflags();                 //寄存器EFLAGS的第18位是AC标志位,386没有(为0)486以上为1
  if ((eflg & EFLAGS_AC_BIT) != 0) { flg486=1; }   /* 386版本的CPU,设定AC=1。当人AC的值还会自动回到0 */
  eflg &= ~EFLAGS_AC_BIT;                 /* AC-bit = 0,与运算,将AC标志位重置为0 (0xffbffff)*/
  io_store_eflags(eflg);
  if (flg486 != 0) {                    //如果是486或以上版本的就禁止缓存
    cr0 = load_cr0();                    //禁止缓存需要对CR0寄存器进行修改
    cr0 |= CR0_CACHE_DISABLE;              //禁止缓存
    store_cr0(cr0);                    //load_cr0和store_cr0都是汇编函数(在naskfunc.nas中)
  }
  i = memtest_sub(start, end);              //内存检查处理,请结合上面写的检查原理看

  if (flg486 != 0) {                    //我们检测完内存之后,把修改的CATCH复原
    cr0 = load_cr0();
    cr0 &= ~CR0_CACHE_DISABLE;             /* 允许缓存 */
    store_cr0(cr0);
   }
  return i;
}
//程序功能:调查从start地址到end地址范围内,能够使用的内存的末尾地址。
//反   转:笔者用异或XOR运算来实现 符号:^
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) {    //i每次增加4,提高速度
    p = (unsigned int *) (i + 0xffc);      //执行p = i;只检查末尾的4个字节
    old = *p;                      /* 先将p的原值保存下来到old中 */
    *p = pat0;                     /* 把0xaa55aa55写到内存中 */
    *p ^= 0xffffffff;                 /* 在内存中反转0xaa55aa55; */
    if (*p != pat1) {                 /* 检查反转的结果 */
      not_memory:
      *p = old;
      break;
    }
    *p ^= 0xffffffff;                 /* 反转结果正确,再次反转 */
    if (*p != pat0) {                 /* 两次反转后,能否回到最初的值 */
      goto not_memory;
    }
    *p = old;                      /* 回复内存该处原来的值,保存在old变量中 */
  }
return i;
}

  最后:在HariMain中调用即可:

//检查范围:00400000--0bfffffff
i = memtest(0x00400000, 0xbfffffff) / (1024 * 1024);
sprintf(s, "memory %dMB", i);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);

  make run一下,啊呀呀,出错啦!笔者煞费苦心地make run 一个错误的内存检查结果:3072MB.

harib06c:
  为什么有3G的内存空间?
  原理思路肯定是没有问题的,要想搞清楚,还得看编译成的汇编到底是什么情况!
  我们来看看上面的这个测试函数memtest_sub()被编译成了什么:
  注  意:请在harib06b里(上一步)执行make -r bootpack.nas 在这一步中,笔者已经把错误解决了。

_memtest_sub:
  PUSH    EBP            ; 压栈,先保存寄存器的内容
  MOV    EBP,ESP;
  MOV    EDX,DWORD [12+EBP]  ; EDX = end ;
  MOV    EAX,DWORD [8+EBP]   ; EAX = start 相当于 i
  CMP    EAX,EDX         ; EAX<=EDX goto L30
  JA    L30;
L36:
L34:
  ADD    EAX,4096        ; EAX += 0X1000 相当于i的值+4
  CMP    EAX,EDX;
  JBE    L36;
L30:
  POP    EBP           ; 出栈,恢复寄存器的内容
  RET;

  好了,我们看到memtest_sub()函数中的反转操作被编译器优化了!
  接下来笔者用汇编重写了该内存检测函数memtest_sub():
  请对照harib06b:部分的memtest_sub()函数看

_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;把0xaa55aa55写到内存中
  MOV    EDI,0x55aa55aa      ; pat1 = 0x55aa55aa;在内存中反转0xaa55aa55
  MOV    EAX,[ESP+12+4]      ; i = start;
mts_loop:
  MOV    EBX,EAX
  ADD    EBX,0xffc          ; p = i + 0xffc 只检查末尾的4个字节
  MOV    EDX,[EBX]          ; old = *p;先将p的原值保存下来到old中
  MOV    [EBX],ESI          ; *p = pat0;把0xaa55aa55写到内存中
  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; 回复内存该处原来的值,保存在old变量中
  ADD    EAX,0x1000         ; i += 0x1000;i每次增加4,提高了循环的速度
  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

harib06d:

  

  什么是内存管理:内存的合理分配和释放。

  笔者其实还是很有心的。为了解决没有操作系统课程基础的读者的理解问题。这一部分笔者首先花了大量篇幅介绍内存管理分段和分页的机制,以及可能遇到的碎片问题(而且还举了一个很详细的例子)。对于有操作系统基础知识,或者理解分段分页机制的读者。可以直接跳到P176代码部分。

  1、笔者内存管理思路:“割舍掉的东西,只要以后还能找回来,就暂时不去管他”
  2、大致可以这样理解:分配(malloc)的内存,只要以后能释放(free)回来,就暂时不去管;
  3、可见:笔者不愿意把时间浪费在内存管理上,毕竟这个系统对内存要求不那么苛刻。好了,我们来看看笔者根据以上思路写的内存管理程序吧:

#define MEMMAN_FREES    4090      /* 32KB,这个是内存管理空间 */
#define MEMMAN_ADDR    0x003c0000 //内存管理空间的地址

struct 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->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;
}
//原理:和FIFO缓冲区处理的方法很相似
//注意:这里没有把内存组织成链表的形式,而是一块一块的。分配内存空间的大小有限制
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) {  //该块内存已经被分配,该块空闲大小为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; /* 失败 */
}
时间: 2024-10-20 11:15:32

《30天自制操作系统》09_day_学习笔记的相关文章

《30天自制操作系统》学习笔记-第1天

为了加深对操作系统的理解,我决定照着<30天自制操作系统>这本书实践一下.项目的github链接是https://github.com/YatesXu/YatesOSASK/ 关于十六进制编辑器 第一个问题就是书中给的十六进制编辑器是日文的,在我的电脑上打开之后是一片乱码,于是我比较之后选用了这个十六进制编辑器wxMEdit,链接是https://wxmedit.github.io/. 另外,visual studio也可以,但是express版不能用,所以还是选用免费开源的软件吧(笑) (伪

《30天自制操作系统》读书笔记(3) 引入C语言

这一次的学习相当曲折, 主要是因为粗心, Makefile里面的错误导致了文件生成出现各种奇奇怪怪的问题, 弄得心力交瘁, 因此制作过程还是尽量按着作者的路子来吧. 作者提供的源码的注释在中文系统下是乱码, 而且代码的分隔用了两个Tab, 在这里要处理一下: :%s/;.*//g 删除所有的注释; :%s/\t\t/\t 把两个Tab替换为一个Tab; 要让作者的nas文件和asm文件拥有相同的语法规则, 在_vimrc文件的最后一行添加 au BufNewFile,BufRead *.nas

《30天自制操作系统》读书笔记(2)hello, world

让系统跑起来 要写一个操作系统,我们首先要有一个储存系统的介质,原版书似乎是06年出版的,可惜那时候没有电脑,没想到作者用的还是软盘,现在的电脑谁有软驱?不得已我使用一张128M的SD卡来代替,而事实上你用的是U盘还是软盘对我们的操作系统没有影响,缺点是你的U盘刷入系统后容量只能是1440 MB,即当年流行的3.5英寸软盘的大小,当然不用担心,再格式化一次(用DiskGeniu),就可以恢复. 我做事情的话,总是怕自己的努力的结果白费了,害怕辛辛苦苦看完这本书但是发现做出来的东西现在根本没法用,

《30天自制操作系统》读书笔记(4) 绘图

暑假果然是滋生懒散的温床. (╯‵□′)╯︵┻━┻ 好久不动都忘记之前做到哪里了, 上次好像做到了C语言的引入, 这一节所做的东西都相当轻松, 将会绘制出操作系统的基本界面. 绘图的原理 按照书中所说, 将值写入到显存中就能在屏幕上显示相应的像素, 在asmhead.nas 中有这一段: 1 CYLS EQU 0x0ff0 ; 设定启动区 2 LEDS EQU 0x0ff1 3 VMODE EQU 0x0ff2 ; 关于颜色数目的信息,颜色的位数 4 SCRNX EQU 0x0ff4 ; 分辨率

《30天自制操作系统》读书笔记(1)读前感

做一个自己的操作系统, 在我看来一直是不可以思议的,而且奇妙的,像是吉他手亲手打造一把自己的吉他? 似乎这个比喻不太恰当, 但是,感觉是一样的. <30天自制操作系统> 为日本的川和秀实先生所著, 有人说他是 "<XX天学会XXX>之类的书中为数不多的几本好书之一." 这本书的优点非常明显,通俗生趣,甚至于有点啰嗦:而且作者无私地提供了源代码而且允许你以任何方式使用,也提供了编译的所有工具,所有东西都是"开箱即用",不容易出问题. 作为后生我

《30天自制操作系统》读书笔记(5) GDT&amp;IDT

梳理项目结构 项目做到现在, 前头的好多东西都忘了, 还是通过Makefile重新理解一下整个项目是如何编译的: 现在我们拥有这么9个文件: ipl10.nas    InitialProgramLoader, 占用了软盘的第一个扇区并符合启动盘的规范, 默认被载入地址是0x7c00 到 0x7e00, 负责将10个柱面读入到0x8200到0x34fff (10个柱面共10*2*18 = 360 个扇区但是第一个没有被读入); asmhead.nas     包含一些暂时未知的设定; naskf

多定时器处理1(30天自制操作系统--读书笔记)

自认为写过很多MCU程序,但总是回头想想,我所了解的MCU编程思想大体有两种,其中具体的想法我得再找时间写下来. 总想总结出一个可扩展的,易移植的写法,但能力还没到这个层次.但<30天自制操作系统>这本书确实给我了一个思路,就像我已经写过的两篇读书笔记. 将两个独立的内容--FIFO和内存动态管理做到高度模块化,尤其是其中数据结构模型的设计更是我学习的好例子. 今天要学习的设计内容是多定时器处理.原书对这部分的处理讲的很详细,由浅入深,看得我由衷佩服作者,也可能是因为我水平低,稍稍看出点门道来

单字节的FIFO缓存(30天自制操作系统--读书笔记)

从今天起,写一些读书笔记.最近几个月都在看<30天自制操作系统这本书>,书虽说看的是电子书,但可以花钱买的正版书,既然花费了金钱,就总得有些收获. 任何人都不能总是固步自封,想要进步就得学习别人的知识,对于程序员而言,最简单的方法即是学习别人的代码. 今天的标题是“单字节的FIFO缓存”,其实就是做一个FIFO,看名字就知道了.也就4个函数和1个相关结构体,这样的小代码在嵌入式系统中很常用,也会很好用. 1.相关数据结构体 struct FIFO8 { unsigned char *buf;

《30天自制操作系统》笔记(12)——多任务入门

<30天自制操作系统>笔记(12)——多任务入门 进度回顾 上一篇介绍了设置显示器高分辨率的方法.本篇讲一下操作系统实现多任务的方法. 什么是多任务 对程序员来说,也许这是废话,不过还是说清楚比较好. 多任务就是让电脑同时运行多个程序(如一边写代码一边听音乐一边下载电影). 电脑的CPU只有固定有限的那么一个或几个,不可能真的同时运行多个程序.所以就用近似的方式,让多个程序轮换着运行.当轮换速度够快(0.01秒),给人的感觉就是"同时"运行了. 多任务之不实用版 我们首先从

《30天自制操作系统》笔记(03)——使用Vmware

<30天自制操作系统>笔记(03)--使用Vmware 进度回顾 在上一篇,实现了用IPL加载OS程序到内存,然后JMP到OS程序这一功能:并且总结出下一步的OS开发结构.但是遇到了真机测试和U盘启动的一些问题.本篇就来解决之. 遇到的问题 物理机测试 简单来说,把软盘(U盘)做成启动盘后,自然想要用来启动物理机器.毕竟这才是真正的测试.(用QEMU总没多大的成就感)但物理机测试的麻烦在于太慢了,每次都要关掉Windows,重启,测试,然后再重启Windows.而且还没办法截图. 而用Vmwa