第09天,内存管理《30天自制操作系统学习笔记》
liebian365 2024-11-27 17:08 19 浏览 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;
}
这段程序的作用:
- 确认CPU类型386,486
- 根据CPU类型设定参数
- 禁止缓存(内存检查时)
- 允许缓存
更新: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
这段代码作用:实现内存检查
- 调查从start地址到end地址的范围内,能够使用的内存的末尾地址
- 首先如果p不是指针,就不能指定 地址去读取内存,所以先执行“p=i;”.
- 紧接着使用这个p,将原值保存下来(变量old)
- 接着试写0xaa55aa55,在内存里反转该值,检查结果是否正确
- 如果正确,就再次反转它,检查一下是否能恢复到初始值。
- 最后,使用old变量,将内存的值恢复回去。
- …如果在某个环节没能 恢复成预想的值,那么就在那个环节终止调查,并报告终止时的地址。
反转 = 用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字节可用
第二种内存管理方法特点:
- 首先占用内存少。804字节管理128MB的内存。
- 管理3GB的内存,只需8KB,为了安全起见,设定10000 条可用区域管理信息,即使这样也只有80KB。
- 大块内存的分配和释放都非常迅速
- 缺点是管理程序变复杂
- 可用空间零散,要么提高空间上限,要么放弃小块内存。
作者采用的内存管理方法:
- 割舍掉的东西,只要以后还能找回 来,就暂时不去管它
更新: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
管理内存是个头疼的事
今天就到这里
相关推荐
- msp的昌伟哥哥(伟昌怎么样)
-
佩戴HoloLens的多个用户可以使用场景共享特性来获取集合视野,并可以与固定在空间中某个位置的同一全息对象进行交互操作。这一切是通过空间锚共享(AnchorSharing)来实现的。为了使用共享服...
- VOculus Rift、Gear VR平台开发者合作申请指南
-
编译/游戏陀螺案山子OculusHome平台——OculusRift和三星Gear主要的应用平台,包括PC版和移动版都可以使用。而现在使用的OculusShare平台,据悉将来也会整合到Ocu...
- 游戏中的"状态机”和"行为树”是什么?
-
状态机是一种模型,用于描述对象在不同状态下的行为和转换。在游戏里,状态机通常用于控制角色或NPC在不同状态下的行为。比如说,一个角色可以有多个状态,比如“待机”、“行走”、“攻击”、“受伤”等,每个状...
- JetBrains Rider现已支持PS5和Xbox主机游戏开发
-
IT之家3月27日消息,Rider是一款由JetBrains出品的跨平台.NETIDE,在2024.3版本中,JetBrainsRider增加了对PlayStation5...
- Unity WebGL 应用开发总结(unity webgl发布)
-
UnityWebGL应用开发总结1.开发环境软件版本Unity2020.1.0f1PyCharm2022.3.2Python3.7.32.编译WebGL对Unity项目进行WebGL编译时...
- 【6.Physics和动画】5.动画(动画电影)
-
5.动画现在,角色可以移动了,但在移动时形象一直不变,对于玩家来说比较生硬,本节中我们让角色在移动时能够播放动画。Unity2D游戏中,角色动画一般采用帧动画的形式来实现。所谓帧动画就是在每一帧显...
- unity3d开发教程-开发环境搭建(unity3d开源项目)
-
一、安装Unity1、从官网下载UnityHub:https://unity.com/download,选择[DownloadforWindows]下载完成后,双击打开安装。一直点...
- 【2.UI元素】3.Panel and Button(ui界面元素)
-
3.PanelandButton3.1PanelPanel(面板)本质上就是预先设置好的Image。可以作为其他UI元素的父级。在层级窗口右击选择UI->Panel即可创建。...
- 揭秘!你玩的字节抖音小游戏制作流程公布
-
1.1注册字节开发者后台1.2Unity版本说明1.3检查AppID是否有效2.1创建项目2.2接入SDK3.1发布安卓Apk3.2发布双端WebGL3.3IOS15.4版本问题字节抖...
- 临时工闯下大祸《糖豆人》源代码更新时不慎泄露
-
这次《糖豆人》工作室Mediatonic的临时工闯下了大祸,在更新时一不留神把游戏的源代码给泄露了。当然,这次泄露之后,官方删除的动作也很快,但是没快过SteamDB创始人PavelDjundik...
- 为3D手游打造, Visual Studio Unity扩展下载
-
IT之家(www.ithome.com):为3D手游打造,VisualStudioUnity扩展下载7月30日消息,微软正式发布升级版VisualStudioToolsforUnity扩...
- 【2.C#基础】3.脚本入门(c# 脚本引擎)
-
3.脚本入门3.1脚本概要在上一节创建的脚本中,包含了一段模板代码,双击工程窗口中的脚本图标,系统自动打开代码编辑器(VSCode)可以看到代码如下图所示:说明:System.Collections...
- unity专题:unitask库(1)(unitypackage)
-
UniTask是一个Unity引擎中的异步编程库,它可以帮助你在Unity项目中编写更简洁、高性能的异步代码。UniTask以Promise/Task的编程模式为基础,提供了与C#...
- 零基础带你看游戏内灰度效果实现原理
-
前言在Unity中实现后处理效果有两种方式:一种是通过使用Unity官方提供的Post-Processing插件。另外一种方式就是使用脚本获取到渲染后帧缓冲区的图像,再通过shader写后处理的效果,...
- 团结引擎自定义Scene视图的层叠面板和工具栏
-
团结引擎提供的了功能,可以为Scene视图添加层叠面板和自定义工具栏,这里学习官方的经典案例。创建层叠面板。总结需要三个步骤:1、创建编辑器脚本(需存放在Editor目录下)2、继承Overlay类,...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)