由于一直想写一个自己的操作系统,网上推荐了《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中

    [html] view plain copy
    1. mov ax,#BOOTSEG  
    2. mov ds,ax  
    3. mov ax,#INITSEG  
    4. mov es,ax  
    5. mov cx,#256  
    6. sub si,si  
    7. sub di,di  
    8. rep  
    9. movsw  
    在这里主要是提醒大家,关于cx的值,由于是要移动一个扇区的数据,一个扇区的大小是512B,这里的cx=256,但是movsw表示移动两个B,所以256*2=512B,这里大家会忽略,没注意;
  • 关于load_setup

    [html] view plain copy
    1. mov dx,#0x0000  
    2. mov cx,#0x0002  
    3. mov bx,#0x0200  
    4. mov ax,#0200+SETUPLEN  
    5. 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

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


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

    [html] view plain copy
    1. read_it:  
    2.     mov ax,es  
    3.     test ax,#0x0fff  
    4. die:    jne die         ! es must be at 64kB boundary  
    5.     xor bx,bx       ! bx is starting address within segment  
    6. rp_read:  
    7.     mov ax,es  
    8.     cmp ax,#ENDSEG      ! have we loaded all yet?  
    9.     jb ok1_read  
    10.     ret  
    11. ok1_read:  
    12.     seg cs  
    13.     mov ax,sectors  
    14.     sub ax,sread  
    15.     mov cx,ax  
    16.     shl cx,#9  
    17.     add cx,bx  
    18.     jnc ok2_read  
    19.     je ok2_read  
    20.     xor ax,ax  
    21.     sub ax,bx  
    22.     shr ax,#9  
    23. ok2_read:  
    24.     call read_track  
    25.     mov cx,ax  
    26.     add ax,sread  
    27.     seg cs  
    28.     cmp ax,sectors  
    29.     jne ok3_read  
    30.     mov ax,#1  
    31.     sub ax,head  
    32.     jne ok4_read  
    33.     inc track  
    34. ok4_read:  
    35.     mov head,ax  
    36.     xor ax,ax  
    37. ok3_read:  
    38.     mov sread,ax  
    39.     shl cx,#9  
    40.     add bx,cx  
    41.     jnc rp_read  
    42.     mov ax,es  
    43.     add ax,#0x1000  
    44.     mov es,ax  
    45.     xor bx,bx  
    46.     jmp rp_read  
    47.   
    48. read_track:  
    49.     push ax  
    50.     push bx  
    51.     push cx  
    52.     push dx  
    53.     mov dx,track  
    54.     mov cx,sread  
    55.     inc cx  
    56.     mov ch,dl  
    57.     mov dx,head  
    58.     mov dh,dl  
    59.     mov dl,#0  
    60.     and dx,#0x0100  
    61.     mov ah,#2  
    62.     int 0x13  
    63.     jc bad_rt  
    64.     pop dx  
    65.     pop cx  
    66.     pop bx  
    67.     pop ax  
    68.     ret  
    69. bad_rt: mov ax,#0  
    70.     mov dx,#0  
    71.     int 0x13  
    72.     pop dx  
    73.     pop cx  
    74.     pop bx  
    75.     pop ax  
    76.     jmp read_track  
    不知道大家在读这段代码的时候是怎么理解的,反正我理解了很长时间,不知道它到底想干什么。网上很多都是注释了每一行,但是没有解释总的在干什么,整体的流程是什么。
  • 标量间代码作用

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


转载的时候请注明出处。

posted on 2017-11-28 21:29  学习记录园  阅读(2272)  评论(0编辑  收藏  举报