终于开始下篇的旅途了,没想到作者在下篇严重高估了读者,全然不像上篇那样把80%的代码拿出来讲解。刚看到第8章的开头偶就云里雾里了,这种感觉很像是第一次看上篇时的感觉,不过还好还好,偶这不是挺过上篇了么,关键还是方法问题。作者在下篇大概只拿出来了5,60%的代码出来讲,这样偶就必须在他的源代码里翻来翻去,全给扒拉出来,他的代码还好有注释,偶还得根据偶的思路来重写并加上注释。。真是辛苦。。下篇只占用了整本书的1/3,还以为很快就能搞定,看来偶真是太天真了。
万事开头难,为了方便调试,首先祭出了ASSERT宏和PANIC宏,这两个宏就是拿来调试的。这次先把ASSERT宏搞定吧。这个宏在C++中相信很多人也用过吧。
首先把此宏的定义找出来,
在include/const.h中:
首先是一个预处理,避免重复定义此宏。如果define了ASSERT,则先是一个函数声明:
此宏有一个参数,如果这个参数为真,则不做处理,如果不为真,则调用先前声明的函数。
接着就调用它,把1个表达式转化为字符串,传进此函数,其它3个参数是被展开的文件名,传递给编译器的那个文件名,和展开成当前的行号,这样就可以方便的知道异常出现的地点。
接着跟进到assertion_failure函数,
lib/misc.c:
首先调用了printl函数,这个函数其实就是printf函数的宏,等于调用了printf函数,printf函数是改进过了的,这个等会再说。函数返回以后,出现一个陀注释,意思是说,如果在系统进程中ASSERT失败,在系统会在printl函数返回halt掉,如果发生在用户进程中,则printl会正常的返回,但是随后又调用了一个spin函数,来看看这个函数:
lib/misc.c:
这个函数调用了printl把参数的串打印出来后就进入了1个死循环。后面的嵌入汇编永远不会执行。
现在就很明了了,就是printl函数也就是printf函数做了什么改进呢,把代码贴出来。
kernel/printf.c
调用的vsprintf函数是关键,贴出来先,注释是我加的。。。乱是乱了点。。
kernel/vsprintf.c
逻辑很清晰,总之是把格式字符串给解析好喽返回去,其中用了一个i2a函数,可以随意设置要转化的进制数,其中用到了递归。。很好。。贴出来先。
kernel/vsprintf.c
printf函数最后调用了printx函数,printx函数就是系统调用咯,跟进去看。
kernel/syscall.asm
把栈中的解析好的串的地址放到edx中给内核使用,接着跟进到内核。
kernel/kernel.asm
先把当前指针压了进去,再把edx压了进去,我们知道这是解析好的串的地址,再把ecx,ebx压了进去,这两个参数在printx函数中是用不着的,这是为了与其它系统调用兼容,日后再说,接着就是中断调用实质性的工作了,跟入。
kernel/proc.c
这里用了一个技巧,如果k_reenter的值为0,则表示ASSERT是在用户进程中调用的,如果k_reenter的值大于0,则表示是在系统进程发生的。在用户进程发生就要把解析好的串的虚拟地址转换为线性地址,由于每个进程的LDT中的段描述符是从GDT中拷来的,只是DPL不一样,所以这里的操作暂时没什么意义,不过也要跟进一下。
这个宏是根据进程的PCB指针得到相对PCB表的偏移
include/proc.h
kernel/proc.c
ldt_seg_linear函数是根据PCB指针和此PCB的LDT的选择子偏移得到此选择子的基地址。
va2la函数是根据进程号即相对PCB表的偏移得到参数void指针的线性地址。
回到sys_printx接着判断解析好的串的第一个字符是否是约定好的字符,如果是PANIC或者ASSERT且在系统进程发生的,则往显存中一直写入解析好的串直到超过显存范围为止。写完后就嵌入汇编halt。
如果是普通的输出或者是在用户进程发生的ASSERT则逐个字符把解析好的串叫给OutChar打印,正常退出。而用户进程的ASSERT结束输出后回到assertion_failure后调用spin函数进入死循环。
这只是非常简单的把作者的代码过了一遍,还得自己写呢。。不过总算有了大概的思路。。明天继续。。