由于一直想写一个自己的操作系统,网上推荐了《linux内核完全注释》。自学了一个星期,感觉这本书还是很好的,同时写下关于内核代码的理解,如果有什么不对的对方,欢迎大家一起来交流。
在内核引导启动程序中,有3个文件,bootsec.s,setup.s head.s。关于这3个源代码,网上有很多人都有详细的解释,但是有很多人的文章中都是对每行代码的解释,但是关于整个代码的整体框架没有很多的解释。在这里我想提出自己对代码的理解,我不会每行都解释,只是对很重要的部分做出自己的理解。
关于bootsec.s主要做了如下的几件事:
- 由于PC开机后,BIOS将可移动的设备的第一个扇区,读入到了0x7c00处,总共512B,bootsec.s就在这里,它将自己移到了ox9000处来执行;
- 初始化堆栈;关于这个堆栈我对它的理解就是在后面的程序,比如read_track中就用到了pop和push之类的指令,因此在这设置了‘
- 将setup.s的模块装载在bootsec.s的后面;
- 获取驱动器的参数,这里应该是值软盘,主要获取到每个磁道的扇区数量;
- 在屏幕中输出“loading system.....”,然后装载system模块即内核模块;
- 确定根文件系统设备;
- 段间跳转,到setup.s中执行;
下面是我对bootsec.s中部分代码的理解。
bootsec.s代码移动到ox9000中
在这里主要是提醒大家,关于cx的值,由于是要移动一个扇区的数据,一个扇区的大小是512B,这里的cx=256,但是movsw表示移动两个B,所以256*2=512B,这里大家会忽略,没注意;关于load_setup
仔细看int 0x13这个中断的各个参数,这里比较重要的是es:bx 将指向setup模块的在内存中的位置。在移动bootsec.s的代码最后有一个jmpi go,INITSEG这个指令,此时的cs=0x900不再时0x7c00了,es=cs,于是es:bx指向了0x900:0200处,从而实现了装载setup模块的功能。
关于ok_load_setup
这里主要是来谈谈这两条指令。从代码的第241行中可以看出sectors是个标量指向一个word长的地址。seg cs表明了sectors的段地址是cs,而不是ds。而且seg cs 的作用范围只有下一行,不会延生到其他地方。
最后介绍这篇文章中最重要的read_it
不知道大家在读这段代码的时候是怎么理解的,反正我理解了很长时间,不知道它到底想干什么。网上很多都是注释了每一行,但是没有解释总的在干什么,整体的流程是什么。标量间代码作用
- ok1_read:主要的功能是确定了ax的值,其实严格的说是确定了al的值,因为al的值是代表了要读取的扇区的值,关于最后的几行代码 这几行代码是我当时极度不能理解的因为当时我认为ax=0,bx=0,最后不都是0吗?:),我想说这几行代码是以后执行的,到时后bx的值不再是0,而是代表了一个段内读取了的数据,sub是不带进位的0-bx正好是64k-段内已读的数据(不得不佩服linus的基本功),从而得到了段内剩余的空间,从而可以求出还可以读取最大的扇区数。
- ok2_read:调用了read_track(),read_track()的功能其实可以看成read_track(ax),根据ax,主要是al来确定一个磁道内从哪个扇区开始读数据,从而读取一个磁道的数据;然后ax=read_track(ax)(伪代码而已),ax表示读取的扇区的值,然后再加上已读的扇区数后比较是否等于每个磁道的扇区数,如果不等,则调用ok3_read(),否则读取下一个磁头1,(软盘有两个磁头,0和1,这和硬盘不一样,硬盘磁头比较多)
- ok4_read:这里为什么不从ok3_read写起呢,主要是因为linus的代码能力的厉害之处,大家慢慢也会懂的,只可意会。ok_read4的主要的功能是重新赋值磁头和ax即扇区的起始地址。
- ok3_read:主要是确定了bx的值和es的值。 这里add bx,cx 是确定了bx的值,即段内已经读取的数据大小,如果bx没有溢出即超过0xffff,则跳转到开始的循环,否则段基址es+=0x1000,再从新循环。
接下了主要讲一下关于我对这段代码的理解。由于软盘只有两个磁头0和1,且只有18个扇区,每个扇区的大小是512B从而代码开始处的流程应该是如下的
- 在ok1_read中从0磁头读取的数目最多(18-6)*512B,不会溢出,则会进入到ok2_read()中;如果bx不等于0,且数值比较接近0xffff时会溢出,则却确定该段内剩余的地址可以读取最大的扇区数;
- 到ok2_read中,从0磁头读取从能够ax开始的整个磁道剩余的扇区数,cx=读取的扇区数,在加上已读的扇区数确定是否全部读完,若是则下一步,否则到4;
- 到ok4_read,磁头变成了1,(只有两个磁头,如果原磁头是1,则增加磁道track),ax=0。
- 到ok3_read中,此时ok3_read的操作是针对下一次循环的,根据这一次的循环设置下一次循环的一些变量,bx=段内已经读取的数据的大小,如果超出64k则增加段基址,由于add是无进位的(这里又体现了linus的高明了);
- 从新返回到1,执行循环,知道es的值>ENDSEG;
到这可以理解为什么我要先介绍ok4_read了,而不是ok3_read了,如果在源代码中把ok3_read放在ok4_read之前那么代码会多加几个jmp命令,虽然便于我们理解,但是代码长度变长了,原来的代码体现了代码的魅力。
转载的时候请注明出处。