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

    由于一直想写一个自己的操作系统,网上推荐了《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,#BOOTSEG
    mov ds,ax
    mov ax,#INITSEG
    mov es,ax
    mov cx,#256
    sub si,si
    sub di,di
    rep
    movsw
    在这里主要是提醒大家,关于cx的值,由于是要移动一个扇区的数据,一个扇区的大小是512B,这里的cx=256,但是movsw表示移动两个B,所以256*2=512B,这里大家会忽略,没注意;
  • 关于load_setup

    mov dx,#0x0000
    mov cx,#0x0002
    mov bx,#0x0200
    mov ax,#0200+SETUPLEN
    int 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 cs
    mov sectors,cx
    这里主要是来谈谈这两条指令。从代码的第241行中可以看出sectors是个标量指向一个word长的地址。seg cs表明了sectors的段地址是cs,而不是ds。而且seg cs 的作用范围只有下一行,不会延生到其他地方。


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

    read_it:
    	mov ax,es
    	test ax,#0x0fff
    die:	jne die			! es must be at 64kB boundary
    	xor bx,bx		! bx is starting address within segment
    rp_read:
    	mov ax,es
    	cmp ax,#ENDSEG		! have we loaded all yet?
    	jb ok1_read
    	ret
    ok1_read:
    	seg cs
    	mov ax,sectors
    	sub ax,sread
    	mov cx,ax
    	shl cx,#9
    	add cx,bx
    	jnc ok2_read
    	je ok2_read
    	xor ax,ax
    	sub ax,bx
    	shr ax,#9
    ok2_read:
    	call read_track
    	mov cx,ax
    	add ax,sread
    	seg cs
    	cmp ax,sectors
    	jne ok3_read
    	mov ax,#1
    	sub ax,head
    	jne ok4_read
    	inc track
    ok4_read:
    	mov head,ax
    	xor ax,ax
    ok3_read:
    	mov sread,ax
    	shl cx,#9
    	add bx,cx
    	jnc rp_read
    	mov ax,es
    	add ax,#0x1000
    	mov es,ax
    	xor bx,bx
    	jmp rp_read
    
    read_track:
    	push ax
    	push bx
    	push cx
    	push dx
    	mov dx,track
    	mov cx,sread
    	inc cx
    	mov ch,dl
    	mov dx,head
    	mov dh,dl
    	mov dl,#0
    	and dx,#0x0100
    	mov ah,#2
    	int 0x13
    	jc bad_rt
    	pop dx
    	pop cx
    	pop bx
    	pop ax
    	ret
    bad_rt:	mov ax,#0
    	mov dx,#0
    	int 0x13
    	pop dx
    	pop cx
    	pop bx
    	pop ax
    	jmp read_track
    
    不知道大家在读这段代码的时候是怎么理解的,反正我理解了很长时间,不知道它到底想干什么。网上很多都是注释了每一行,但是没有解释总的在干什么,整体的流程是什么。
  • 标量间代码作用

  1. ok1_read:主要的功能是确定了ax的值,其实严格的说是确定了al的值,因为al的值是代表了要读取的扇区的值,关于最后的几行代码
    xor ax,ax
    sub ax,bx
    shr 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,ax
    shl cx,#9
    add bx,cx
    jnc rp_read
    mov ax,es
    add ax,#0x1000
    mov es,ax
    xor bx,bx
    jmp 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命令,虽然便于我们理解,但是代码长度变长了,原来的代码体现了代码的魅力。


转载的时候请注明出处。


  

  

你可能感兴趣的:(Linux,0.11,源码看书笔记)