3 内存容量检查(2)(harib06c)
这种做法本身没有问题,笔者在OSASK上确认过,所以看到上述结果很纳闷。这种内存检查方法在很多机型上都能运行,所以笔者非常自信地向大家推荐了它。虽然笔者坚信程序没有问题,可运行结果……
经过多方调查,终于搞清楚了原因。如果我们不用“make run”,而是用“make -r bootpack.nas”来运行的话,就可以确认bootpack.c被编译成了什么样的机器语言。用文本编辑器看一看生成的bootpack.nas会发现,最下边有memtest_sub的编译结果。我们将编译结果列在下面。(为了读起来方便,笔者还添加了注释。)
harib06b中,memtest_sub的编译结果
_memtest_sub:
PUSH EBP ; C编译器的固定语句
MOV EBP,ESP
MOV EDX,DWORD [12+EBP] ; EDX = end;
MOV EAX,DWORD [8+EBP] ; EAX = start; /* EAX是i */
CMP EAX,EDX ; if (EAX > EDX) goto L30;
JA L30
L36:
L34:
ADD EAX,4096 ; EAX += 0x1000;
CMP EAX,EDX ; if (EAX <= EDX) goto L36;
JBE L36
L30:
POP EBP ; 接收前文中PUSH的EBP
RET ; return;
有些细节大家可能不太明白,但是可以跟memtest_sub比较一下。可以发现,以上的编译结果有点不正常。
harib06b中,memtest_sub的内容
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;
}
大家会发现,编译后没有XOR等指令,而且,好像编译后只剩下了for语句。怪不得显示结果是3GB呢。但是,为什么会这样呢?
■■■■■
笔者开始以为这是C编译器的bug,但仔细查一查,发现并非如此。反倒是编译器太过优秀了。
编译器在编译时,应该是按下面思路考虑问题的。
首先将内存的内容保存到old里,然后写入pat0的值,再反转,最后跟pat1进行比较。这不是肯定相等的吗?if语句不成立,得不到执行,所以把它删掉。怎么?下面还要反转吗?这家伙好像就喜欢反转。这次是不是要比较p和pat0呀?这不是也肯定相等吗?这些处理不是多余么?为了提高速度,将这部分也删掉吧。这样一来,程序就变成了:
编译器脑中所想的(1)
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; / 反转 /
p ^= 0xffffffff; / 再次反转 /
p = old; / 恢复为修改前的值 /
}
return i;
}
反转了两次会变回之前的状态,所以这些处理也可以不要嘛。因此程序就变成了这样:
编译器脑中所想的(2)
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 = old; / 恢复为修改前的值 /
}
return i;
}
还有,“p = pat0;”本来就没有意义嘛。反正要将old的值赋给p。因此程序就变成了:
编译器脑中所想的(3)
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 = old; / 恢复为修改前的值 /
}
return i;
}
这程序是什么嘛?结果,p里面不是没写进任何内容吗?这有什么意义?
编译器脑中所想的(4)
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);
}
return i;
}
这里的地址变量p,虽然计算了地址,却一次也没有用到。这么说来,old、pat0、pat1也都是用不到的变量。全部都舍弃掉吧。
编译器脑中所想的(5)
unsigned int memtest_sub(unsigned int start, unsigned int end)
{
unsigned int i;
for (i = start; i <= end; i += 0x1000) { }
return i;
}
好了,这样修改后,速度能提高许多。用户肯定会说:“这编译器真好,速度特别快!”
根据以上编译器的思路,我们可以看出,它进行了最优化处理。但其实这个工作本来是不需要的。用于应用程序的C编译器,根本想不到会对没有内存的地方进行读写。
如果更改编译选项,是可以停止最优化处理的。可是在其他地方,我们还是需要如此考虑周密的最优化处理的,所以不想更改编译选项。那怎样来解决这个问题呢?想来想去,还是觉得很麻烦,于是决定memtest_sub也用汇编来写算了。
这次C编译器只是好心干了坏事,但意外的是,它居然会考虑得如此周到、缜密来进行最优化处理……这个编译器真是聪明啊!顺便说一句,这种事偶尔还会有的,所以能够看见中途结果很有用。而且懂汇编语言很重要。
■■■■■
笔者用汇编语言写的程序列举如下。
本次的naskfunc.nas节选
_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
笔者好久没写过这么长的汇编程序了。程序里加上了足够的注释,应该很好懂。虽然XOR指令(异或)是第一次出现,不过不用特别解释大家也应该能明白。
那好,我们删除bootpack.c中的memtest_sub函数,运行一下看看。“make run”。结果怎么样呢?
内存容量显示正常了
太好了!现在可以回到内存管理这个正题上来了。