1. 内存容量检查
- 由于内存和CPU的距离比CPU内部元件之间的距离要远很多, 因此,在寄存器内部使用 mov 要比 从寄存器MOV 到内存快很多
- 另外, 由于寄存器的速度快, 但是存储的容量非常有限, 所以不得不频繁的使用内存
- 为了解决这个问题, IBM 引入了 高速缓存存储器 (cache memory), 但是成本非常高
- 本质上呢, 我们可以讲这个高速缓存器件理解成是一个类似缓冲区的概念
2. 内存容量检查思路
- 内存检查的时候, 我们可以往内存中随便写入一个值, 然后马上读取, 来检测读取的值和写入的值是否是相等的。 如果内存连接正常, 那么写入的值应该可以存放在内存中。 但是如果没有连接上的话, 读出来的值一定是乱七八糟的。
为保证检查的有效性, 我们必须关闭缓存。 - 首先, 我们通过将cpu 的EFLAGS 的第 18 bit 设置为1, ie, 使用缓存,对于 i486 以上的cpu 是可以设置成功的, 而对于 i486 以下的cpu, 是不能设置成功的, 通过这个可以判断 cpu 类型
- 下面代码检测速度有些慢, 可以考虑修改增量 为 4KB
- 效果:
- 相关代码如下:
#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;
/* 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にしても自動で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;
}
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;
}
3. 反思
- 其实上面的结果是不对的, 我们实际的内存容量应该只有32MB
通过
make -r bootpack.nas
可以获取bootpack.c 的汇编文件, 通过查看这个文件, 我们发现:_memtest_sub: PUSH EBP MOV EBP,ESP MOV EDX,DWORD [12+EBP] MOV EAX,DWORD [8+EBP] CMP EAX,EDX JA L30 L36: L34: ADD EAX,4096 CMP EAX,EDX JBE L36 L30: POP EBP RET
显然智能的gcc 编译器, 已经为我们做了很多不必要的优化操作
为了避免编译器自动优化, 这里考虑使用汇编来编写内存检测代码
_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
- 效果:
4. 挑战内存管理
- 内存管理的基础, 一个是内存分配, 另一个是内存释放
- 一种方式是使用 位图管理表, 但是缺点是浪费空间
- 另一种方式是使用列表管理方式
- 这种方式优点: 占用内存少, 并且大块的内存分配和释放比较迅速
- 缺点: 管理程序变得复杂了, 另外当可用空间变得零零散散的时候, 列表空间可能被用完。。。
- 这里采用第二种方式进行处理, 相应代码: