zoukankan      html  css  js  c++  java
  • 《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; /* 失败 */
    }
  • 相关阅读:
    牛客(46)孩子们的游戏(圆圈中最后剩下的数)
    牛客(45)扑克牌顺子
    牛客(44)翻转单词顺序列
    牛客(43)左旋转字符串
    牛客(42)和为S的两个数字
    牛客(41)和为S的连续正数序列
    牛客(40)数组中只出现一次的数字
    牛客(39)平衡二叉树
    牛客(38)二叉树的深度
    牛客(37)数字在排序数组中出现的次数
  • 原文地址:https://www.cnblogs.com/pengfeiz/p/5796872.html
Copyright © 2011-2022 走看看