百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

第09天,内存管理《30天自制操作系统学习笔记》

liebian365 2024-11-27 17:08 2 浏览 0 评论

管理内存本质是库管(仓库管理)


整理源文件

整理bootpack.c



新建:keyboard.c和mouse.c

  • wait_KBC_sendready = 从bootpack.c 移动到 keyboard.c
  • nit_keyboard = 从bootpack.c 移动到 keyboard.c
  • enable_mouse = 从bootpack.c 移动到mouse.c
  • mouse_decode = 从bootpack.c 移动到mouse.c
  • inthandler21 = 从init.c 移动到 keyboard.c
  • nthandler2c = 从init.c 移动到 mouse.c



更新:bootpack.h
增加了keyborad.c和mouse.c声明

...代码省略

/* keyboard.c */
void inthandler21(int *esp);
void wait_KBC_sendready(void);
void init_keyboard(void);
extern struct FIFO8 keyfifo;
#define PORT_KEYDAT		0x0060
#define PORT_KEYCMD		0x0064

/* mouse.c */
struct MOUSE_DEC {
	unsigned char buf[3], phase;
	int x, y, btn;
};
void inthandler2c(int *esp);
void enable_mouse(struct MOUSE_DEC *mdec);
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat);
extern struct FIFO8 mousefifo;



更新:Makfile

补入了keyboard.obj mouse.obj

OBJS_BOOTPACK = bootpack.obj naskfunc.obj hankaku.obj graphic.obj dsctbl.obj \
		int.obj fifo.obj keyboard.obj mouse.obj

......代码省略




检查内存容量

  • 内存多大?
  • 内存范围?

往内存里写入数据时,如果先写入内存的话,在等待写入完成的期间,CPU处于空闲状态,这样就会影响速度。所以,先更新高速缓存,缓存控制电路配合内存的速度,然后再慢慢发送内存写入命令。

观察机器语言的流程会发现,9成以上的时间耗费在循环上。所谓循环,是指程序在同一个地方来回打转。所以,那个地方的内存要一遍又一遍读进来。从第2圈循环开始,那个地方的内存信息已经保存到缓存里了,就不需要执行费时的读取内存操作了,机器语言的执行速度因而得以大幅提高。



更新:bootpack.c

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

这段程序的作用:

  1. 确认CPU类型386,486
  2. 根据CPU类型设定参数
  3. 禁止缓存(内存检查时)
  4. 允许缓存



更新:naskfunc.nas

为了禁止缓存,需要对CR0寄存器进行操作,由于无法使用C语言编写,所以写道nas

_load_cr0: ; int load_cr0(void);
        MOV EAX,CR0
        RET
_store_cr0: ; void store_cr0(int cr0);
        MOV EAX,[ESP+4]
        MOV CR0,EAX
        RET

更新:naskfunc.nas

关于memtest_sub这个功能在c语言中有问题,所以用汇编语言编写,这个错误纠正过程大家看看书,这里就不细写了。

_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

这段代码作用:实现内存检查

  1. 调查从start地址到end地址的范围内,能够使用的内存的末尾地址
  2. 首先如果p不是指针,就不能指定 地址去读取内存,所以先执行“p=i;”.
  3. 紧接着使用这个p,将原值保存下来(变量old)
  4. 接着试写0xaa55aa55,在内存里反转该值,检查结果是否正确
  5. 如果正确,就再次反转它,检查一下是否能恢复到初始值。
  6. 最后,使用old变量,将内存的值恢复回去。
  7. …如果在某个环节没能 恢复成预想的值,那么就在那个环节终止调查,并报告终止时的地址。

反转 = 用XOR运算来实现,其运算符是“^”。
“*p^ = 0xffffffff;”是“*p = *p^0xffffffff;”的省略形式。

更新:bootpack.c

i = memtest(0x00400000, 0xbfffffff) / (1024 * 1024);
sprintf(s, "memory %dMB", i);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);

对0x00400000~0xbfffffff范围的内存进行检查。这 个程序最大可以识别3GB范围的内存。


测试:打开cmd,输入make run

文件夹09_day\harib06c


挑战内存管理

内存管理的意义?

  • 内存分配:启动程序,分配空闲内存地址
  • 内存释放:程序关闭,归还内存地址,以便再次分配

比如说,假设内存128MB,程序A需要100KB,画面控制需要1.2MB。像这样,需要分配一定大小的内存。为了应付这些需求,必须管理好哪些内存空闲,哪些内存正在使用,这就是内存管理。否则会变得一团糟。

内存管理方法1:

  • 假设有128MB的内存(即有0x08000000个字节)。
  • 假设以0x1000个字节(4KB)为单位进行管理。
  • 0x08000000/0x1000 = 0x08000 = 创建32768字节区域。
  • 写入0标记位空闲,写入1标记为正在使用
char a[32768];
for (i = 0; i < 1024; i++) {
		a[i] = 1; 													/* 一直到4MB为止,标记为正在使用 */
}
for (i = 1024; i < 32768; i++) {
		a[i] = 0;													 /* 剩下的全部标记为空 */
}


  • 假设需要使用100kb空间
  • 从a中找出连续25个标记为0的地址
j = 0;
再来一次:
for (i = 0; i < 25; i++) {
        if (a[j + i] != 0) {
                j++;
                if (j < 32768 - 25) goto 再来一次;
                "没有可用内存了";
         }
}
"从a[j]到a[j + 24]为止,标记连续为0";


  • 假设找到连续25个标记为0的地址
  • 暂时标记为1(正在使用)
  • 然后从j值计算出对应地址
  • 以0x1000字节为管理单位
for (i = 0; i < 25; i++) {
				a[j + i] = 1;
}
"从 j * 0x1000 开始的100KB空间得到分配";


  • 假设要释放这部分内存空间(从0x00123000开始的100kb)
  • 用0x00123000除以0x1000计算出j
j = 0x00123000 / 0x1000;
for (i = 0; i < 25; i++) {
		a[j + i] = 0;
}


一些bug

  • 如果内存是128MB,管理表只需要32768字节(32KB);
  • 如果内存最大是3GB,0xc0000000 / 0x1000 = 0xc0000 = 786432(768KB)

如果容量是个问题,这个管理表可以不用char来构成, 而是使用位(bit)来构成。归根到底,储存的只有0和1.这样做,程序会变得复杂些,但是管理表的大小可缩减到原来的1/8。

如果是3GB内存,只需要96KB就可以管理整个内存了

内存管理方法2:

列表管理法:把类似于“从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 = 0x00400000; /* 从0x00400000号地址开始,有124MB可用 */
        memman.free[0].size = 0x07c00000;

之所以有1000个free,是考虑到即使可用内存部分不连续,我们也能写入到这1000个free里


  • 假设需要100kb空间
  • 只需查看memman中free状态
  • 从中找出到100KB以上可用空间即可
for (i = 0; i < memman.frees; i++) {
      if (memman.free[i].size >= 100 * 1024) {
      "找到可用空间!";
      "从地址memman.free[i].addr开始的100KB空间,可以使用哦!";
      }
}


  • 如果找到可用内存空间
  • 将这段信息从“可用内存空间管理表”中删除,相当于“正在使用”
  • 如果size变成了0,将这条信息删掉 除,frees减去1就可以了。
memman.free[i].addr += 100 * 1024; /* 可用地址向后推进了100KB */
memman.free[i].size -= 100 * 1024; /* 减去100KB */


  • 释放内存,增加一条可用信息,frees加1
  • 调查这段新释放的内存与相邻可用空间能否连到一起
  • 如果能连到一起,归纳为一条
  • 如果不归纳为一条,系统索要内存空间时,找不到释放的可用空间
free[0]:地址0x00400000号开始,0x00019000字节可用
free[1]:地址0x00419000号开始,0x07be7000字节可用

可以归纳为
free[0]:地址0x00400000号开始,0x07c00000字节可用


第二种内存管理方法特点:

  1. 首先占用内存少。804字节管理128MB的内存。
  2. 管理3GB的内存,只需8KB,为了安全起见,设定10000 条可用区域管理信息,即使这样也只有80KB。
  3. 大块内存的分配和释放都非常迅速
  4. 缺点是管理程序变复杂
  5. 可用空间零散,要么提高空间上限,要么放弃小块内存。


作者采用的内存管理方法:

  • 割舍掉的东西,只要以后还能找回 来,就暂时不去管它

更新:bootpack.c

#define MEMMAN_FREES 4090 /* 大约是32KB*/

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

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; /* 没有可用空间 */
}
  • struct MEMMAN,创建了4000组,这样一来,管理空间大约是32KB
  • memman_init对memman进行了初始化,将frees设为0
  • memman_total用来计算可用内存的合计大小并返回
  • memman_alloc功能是分配指定大小的内存
man->free[i].addr = man->free[i+1].addr;
man->free[i].size = man->free[i+1].size;

我们在这里将其归纳为了:
man->free[i] = man->free[i+1];


释放内存功能

更新:bootpack.c

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

程序的解释在注释中


功能写好了要在bootpack.c中使用

#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);

......代码省略
  • memman需要32KB,决定使用自0x003c0000开始的 32KB
  • 然后计算内存总量 memtotal,
  • 将现在不用的内存以0x1000个字节为单位注册到memman 里。
  • 最后,显示出合计可用内存容量。
  • 在QEMU上执行时,有时会注册 成632KB和28MB。632+28672=29304,所以屏幕上会显示出29304KB。

测试:打开cmd,输入make run


管理内存是个头疼的事

今天就到这里

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: