先啰嗦几句
整整一年之后开始”第9天”的学习,这次读的时候,和一年前的感觉区别很大,一年前大量的力气花在理解作者对一些理论的阐述,问题的解释,现在理解起来则容易的多,更多的是再验证一年来学的《计算机组成》,《操作系统》的知识;不变的是对这本书很好的评价,一年前觉得这本书是图文并茂,讲解清晰,讲其然,讲其所以然;一年后,突出感觉这是一本讲究实践的书,作者在书中展现了制作一个操作系统的思路,每一步是如何考虑的,而且把很多优秀的实践融合在里面,隐含很多思想,比如封装,模块化,数据结构等等,对于初学者是一种润物无声的影响,简直是打通“任督二脉”。这次读和之前方式不太一样,之前是,看懂之后,代码要自己尝试在敲一遍,这一次只读不写代码,而且我也打算把剩下的20章挑出几章来看,一是时间紧张有更多事情要做,另一方面这个系统虽然五脏俱全,但毕竟是一个“toy os“,距离unix,linux这些系统毕竟差别太多,而且作者花费时间较多的图形界面的工作也并不是我最关心的。在读这本书有”善始善终“的想法,另一方面也是为进一步的学习打个基础。
正文部分
内存管理讲的两部分,容量检查,对内存的管理,作者的讲解已经非常清晰,所以这里不会重复,我会记录我个人对一些细节的理解,可能会有些和内存管理的题目跑偏
容量检查
作者不想调用各种各样的BIOS函数,所以自己写程序检查。容量检查的做法是,从内存起始位置开始,写到地址一个数据,再读回来,两次一样内存就是正常的,地址递增,直到出现不一致,说明内存地址结束,从而确定大小(开机自检时,内存错误已经被检查)
去除Cache的干扰
Cache是用于解决CPU和内存速度不匹配的问题,是一种高速存储器,它保存的是内存内容的副本。基于局部性原理,系统会把将要访问的内存内容装到cache里,CPU下一次访问内存时直接读取cache内容,提高速度,同样如果CPU需要多次修改同一内存地址处的内容,也是先修改cache的内容,最后在写回内存(当然需要一个内存与Cache的映射机制来支持这一做法)。所以如果不禁用Cache的话,对于上文提到的内存检查的方法是有干扰的。
涉及到关闭Cache,C程序就无能为力了,所以需要C+汇编混合编程
C和汇编混合,在nask编译器里,要注意的就是汇编代码的函数要加GLOBAL声明,而且lable前面要加’_’才能很好的和C中的函数链接,Makefile也要把汇编编译的obj和C编译的obj链接起来,看第3天里面的一个例子
naskfunc.nas
; naskfunc
; TAB = 4
[FORMAT "WCOFF"] ; 制作目标文件的模式
[BITS 32] ; 制作32位模式使用的机械语言
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 源文件名信息
GLOBAL _io_hlt ; 程序中包含的函数名
; 以下是实际的函数
[SECTION .text] ;目标文件中写了这些之后在写程序
_io_hlt : ; void io_hlt(void);
HLT
RET
bootpak.c
/*告诉C编译器,有一个函数在别的文件里*/
void io_hlt(void);
void HariMain(void)
{
fin:
io_hlt(); /*执行naskfunc.nas里的io_hlt*/
goto fin;
}
Makefile中的有关部分
MAKE = $(TOOLPATH)make.exe -r
NASK = $(TOOLPATH)nask.exe
CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM = $(TOOLPATH)obj2bim.exe
bootpack.gas : bootpack.c Makefile
$(CC1) -o bootpack.gas bootpack.c
bootpack.nas : bootpack.gas Makefile
$(GAS2NASK) bootpack.gas bootpack.nas
bootpack.obj : bootpack.nas Makefile
$(NASK) bootpack.nas bootpack.obj bootpack.lst
naskfunc.obj : naskfunc.nas Makefile
$(NASK) naskfunc.nas naskfunc.obj naskfunc.lst
bootpack.bim : bootpack.obj naskfunc.obj Makefile
$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
bootpack.obj naskfunc.obj
内存检查代码
486以上的CPU才有缓存,所以要先检查是386还是486,可以看到代码以有一个很”反复“的检查过程,可能是为了确保吧
#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; /*禁止Cache*/
store_cr0(cr0);
}
i = memtest_sub(start, end);
if (flg486 != 0) {
cr0 = load_cr0();
cr0 &= ~CR0_CACHE_DISABLE; /*允许缓存*/
store_cr0(cr0);
}
return i;
}
还有一个地方值得注意,在内存检查完成之后,要把禁用cache关掉回去,这种类似于”申请—使用—释放“的形式还有很多,比如指针的使用,使用winsock时使用WSACleanup()释放socket等等。
编译器优化造成的bug
作者这个bug虽然一句”终于搞清楚了原因“轻描淡写的带过了debug的过程,相必他在调试的时候一定相当蛋疼,不过他给出了一种调试的方法,查看C语言汇编之后的汇编代码,查看程序到底执行了哪些指令,作者调bug竟然都想到了这样的调试方法,深表同情 ; (
下面把C程序,汇编之后的代码,以及用汇编重写之后的代码贴在这里
C
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;
}
相应的汇编
_memset_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: ; 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
内存容量检查部分到此为止
内存管理
作者使用的是一种分区管理的方式,以0x1000字节为单位进行管理,作者的代码,分析都非常清晰。
作者特别提到了一个向上取整的技巧,记录在此
以0x1000为单位向上取整数(只对2进制数有效)
i = (i + 0xfff ) & 0xfffff000; //2进制数的向上舍入
i = i - (i % 1000); //10进制数的向下舍入
说明
本文由giantpoplar发表于CSDN
地址
http://blog.csdn.net/giantpoplar/article/details/48227299
转载请保留本说明