linux0.11源码学习——bootsect.s学习

来源:互联网 发布:android 仿淘宝详情页 编辑:程序博客网 时间:2024/04/30 02:54

    由于一直想写一个自己的操作系统,网上推荐了《linux内核完全注释》。自学了一个星期,感觉这本书还是很好的,同时写下关于内核代码的理解,如果有什么不对的对方,欢迎大家一起来交流。

   在内核引导启动程序中,有3个文件,bootsec.s,setup.s head.s。关于这3个源代码,网上有很多人都有详细的解释,但是有很多人的文章中都是对每行代码的解释,但是关于整个代码的整体框架没有很多的解释。在这里我想提出自己对代码的理解,我不会每行都解释,只是对很重要的部分做出自己的理解。

关于bootsec.s主要做了如下的几件事:

  1. 由于PC开机后,BIOS将可移动的设备的第一个扇区,读入到了0x7c00处,总共512B,bootsec.s就在这里,它将自己移到了ox9000处来执行;
  2. 初始化堆栈;关于这个堆栈我对它的理解就是在后面的程序,比如read_track中就用到了pop和push之类的指令,因此在这设置了‘
  3. 将setup.s的模块装载在bootsec.s的后面;
  4. 获取驱动器的参数,这里应该是值软盘,主要获取到每个磁道的扇区数量;
  5. 在屏幕中输出“loading system.....”,然后装载system模块即内核模块;
  6. 确定根文件系统设备;
  7. 段间跳转,到setup.s中执行;

下面是我对bootsec.s中部分代码的理解。

  • bootsec.s代码移动到ox9000中

    mov ax,#BOOTSEGmov ds,axmov ax,#INITSEGmov es,axmov cx,#256sub si,sisub di,direpmovsw
    在这里主要是提醒大家,关于cx的值,由于是要移动一个扇区的数据,一个扇区的大小是512B,这里的cx=256,但是movsw表示移动两个B,所以256*2=512B,这里大家会忽略,没注意;
  • 关于load_setup

    mov dx,#0x0000mov cx,#0x0002mov bx,#0x0200mov ax,#0200+SETUPLENint ox13
    仔细看int 0x13这个中断的各个参数,这里比较重要的是es:bx 将指向setup模块的在内存中的位置。在移动bootsec.s的代码最后有一个jmpi go,INITSEG这个指令,此时的cs=0x900不再时0x7c00了,es=cs,于是es:bx指向了0x900:0200处,从而实现了装载setup模块的功能。
  • 关于ok_load_setup

    seg csmov sectors,cx
    这里主要是来谈谈这两条指令。从代码的第241行中可以看出sectors是个标量指向一个word长的地址。seg cs表明了sectors的段地址是cs,而不是ds。而且seg cs 的作用范围只有下一行,不会延生到其他地方。


  • 最后介绍这篇文章中最重要的read_it

    read_it:mov ax,estest ax,#0x0fffdie:jne die! es must be at 64kB boundaryxor bx,bx! bx is starting address within segmentrp_read:mov ax,escmp ax,#ENDSEG! have we loaded all yet?jb ok1_readretok1_read:seg csmov ax,sectorssub ax,sreadmov cx,axshl cx,#9add cx,bxjnc ok2_readje ok2_readxor ax,axsub ax,bxshr ax,#9ok2_read:call read_trackmov cx,axadd ax,sreadseg cscmp ax,sectorsjne ok3_readmov ax,#1sub ax,headjne ok4_readinc trackok4_read:mov head,axxor ax,axok3_read:mov sread,axshl cx,#9add bx,cxjnc rp_readmov ax,esadd ax,#0x1000mov es,axxor bx,bxjmp rp_readread_track:push axpush bxpush cxpush dxmov dx,trackmov cx,sreadinc cxmov ch,dlmov dx,headmov dh,dlmov dl,#0and dx,#0x0100mov ah,#2int 0x13jc bad_rtpop dxpop cxpop bxpop axretbad_rt:mov ax,#0mov dx,#0int 0x13pop dxpop cxpop bxpop axjmp read_track
    不知道大家在读这段代码的时候是怎么理解的,反正我理解了很长时间,不知道它到底想干什么。网上很多都是注释了每一行,但是没有解释总的在干什么,整体的流程是什么。
  • 标量间代码作用

  1. ok1_read:主要的功能是确定了ax的值,其实严格的说是确定了al的值,因为al的值是代表了要读取的扇区的值,关于最后的几行代码
    xor ax,axsub ax,bxshr ax,#9
    这几行代码是我当时极度不能理解的因为当时我认为ax=0,bx=0,最后不都是0吗?:),我想说这几行代码是以后执行的,到时后bx的值不再是0,而是代表了一个段内读取了的数据,sub是不带进位的0-bx正好是64k-段内已读的数据(不得不佩服linus的基本功),从而得到了段内剩余的空间,从而可以求出还可以读取最大的扇区数。
  2. ok2_read:调用了read_track(),read_track()的功能其实可以看成read_track(ax),根据ax,主要是al来确定一个磁道内从哪个扇区开始读数据,从而读取一个磁道的数据;然后ax=read_track(ax)(伪代码而已),ax表示读取的扇区的值,然后再加上已读的扇区数后比较是否等于每个磁道的扇区数,如果不等,则调用ok3_read(),否则读取下一个磁头1,(软盘有两个磁头,0和1,这和硬盘不一样,硬盘磁头比较多)
  3. ok4_read:这里为什么不从ok3_read写起呢,主要是因为linus的代码能力的厉害之处,大家慢慢也会懂的,只可意会。ok_read4的主要的功能是重新赋值磁头和ax即扇区的起始地址。
  4. ok3_read:主要是确定了bx的值和es的值。
    mov sread,axshl cx,#9add bx,cxjnc rp_readmov ax,esadd ax,#0x1000mov es,axxor bx,bxjmp rp_read
    这里add bx,cx 是确定了bx的值,即段内已经读取的数据大小,如果bx没有溢出即超过0xffff,则跳转到开始的循环,否则段基址es+=0x1000,再从新循环。
  • 整个代码流程

                            接下了主要讲一下关于我对这段代码的理解。由于软盘只有两个磁头0和1,且只有18个扇区,每个扇区的大小是512B从而代码开始处的流程应该是如下的

  1. 在ok1_read中从0磁头读取的数目最多(18-6)*512B,不会溢出,则会进入到ok2_read()中;如果bx不等于0,且数值比较接近0xffff时会溢出,则却确定该段内剩余的地址可以读取最大的扇区数;
  2. 到ok2_read中,从0磁头读取从能够ax开始的整个磁道剩余的扇区数,cx=读取的扇区数,在加上已读的扇区数确定是否全部读完,若是则下一步,否则到4;
  3. 到ok4_read,磁头变成了1,(只有两个磁头,如果原磁头是1,则增加磁道track),ax=0。
  4. 到ok3_read中,此时ok3_read的操作是针对下一次循环的,根据这一次的循环设置下一次循环的一些变量,bx=段内已经读取的数据的大小,如果超出64k则增加段基址,由于add是无进位的(这里又体现了linus的高明了);
  5. 从新返回到1,执行循环,知道es的值>ENDSEG;


    到这可以理解为什么我要先介绍ok4_read了,而不是ok3_read了,如果在源代码中把ok3_read放在ok4_read之前那么代码会多加几个jmp命令,虽然便于我们理解,但是代码长度变长了,原来的代码体现了代码的魅力。


转载的时候请注明出处。


  

  

0 0
原创粉丝点击