linux0.11内核boot分析

 

1)bootsect.s源码总结
bootsect.s编译后的二进制代码存放在磁盘的第一个扇区(引导区),开机启动后会被BIOS加载到内存的0x07c0处,
并跳转到0x07c0处执行。下面开始解释程序源码

 <1>bootsect.s将自身从0x07c0处移动到0x9000处
 entry _start
 _start:
  mov     ax,#BOOTSEG !BOOTSEG=0x07c0,也即bootsect.s被BIOS加载到内存中的位置  
  mov     ds,ax 
  mov     ax,#INITSEG     !INITSEG=0x9000,也即bootsect.s即将移动到的位置
  mov     es,ax
  mov     cx,#256              !一个扇区占用512b,由于使用串传送指令movw,因此将循环置为256
  sub     si,si
  sub     di,di
  rep
  movw                              !串传送指令,每次传送一个字的长度
  jmpi    go,INITSEG       !长跳转指令,INITSEG -> cs : go -> ip
 
 <2>加载setup.s
 ! 利用BIOS 中断INT 0x13 将setup 模块从磁盘第2个扇区,开始读到0x90200开始处,共读4个扇区。
 ! 如果读出错,则复位驱动器,并重试,没有退路。INT 0x13 的使用方法如下:
 ! 读扇区:
 ! ah = 0x02 - 读磁盘扇区到内存;al = 需要读出的扇区数量;
 ! ch = 磁道(柱面)号的低8位; cl = 开始扇区(0-5 位),磁道号高2位(6-7);
 ! dh = 磁头号; dl = 驱动器号(如果是硬盘则位7 要置位);
 ! es:bx 指向数据缓冲区; 如果出错则CF 标志置位。
 ! ah = 0x00 - 重置磁盘驱动器.
 load_setup:
        mov     dx,#0x0000              ! drive 0, head 0
        mov     cx,#0x0002              ! sector 2, track 0
        mov     bx,#0x0200              ! address = 512, in INITSEG
        mov     ax,#0x0200+SETUPLEN     ! service 2, nr of sectors
        int     0x13                    ! read it
        jnc     ok_load_setup           ! ok - continue
        mov     dx,#0x0000
        mov     ax,#0x0000              ! reset the diskette
        int     0x13
        j       load_setup

 <3>获取磁盘驱动器的参数。使用BIOS中断 INT 0x13,功能ah = 0x08来获取 dl 驱动器的参数信息。
 ! 取磁盘驱动器的参数,特别是每道的扇区数量。
 ! 取磁盘驱动器参数INT 0x13 调用格式和返回信息如下:
 ! ah = 0x08 dl = 驱动器号(如果是硬盘则要置位7 为1)。
 ! 返回信息:
 ! 如果出错则CF 置位,并且ah = 状态码。
 ! ah = 0, al = 0, bl = 驱动器类型(AT/PS2)
 ! ch = 最大磁道号的低8 位,cl = 每磁道最大扇区数(位0-5),最大磁道号高2 位(位6-7)
 ! dh = 最大磁头数, dl = 驱动器数量,
 ! es:di  -> 软驱磁盘参数表。

 mov dl,#0x00
 mov ax,#0x0800   ! AH=8 is get drive parameters
 int 0x13
 mov ch,#0x00
 seg cs    ! 表示下一条语句的操作数在cs 段寄存器所指的段中。
 mov sectors,cx   ! 保存每磁道扇区数。cx 是驱动器每道的最大扇区数。
 mov ax,#INITSEG
 mov es,ax   ! 因为上面取磁盘参数中断改掉了es 的值,这里重新改回。

 --注解:由于system模块比较的大,而int 0x13中断需要指定数据所在的磁道信息,因此需要知道每个磁道的扇区数,进而计算数据分布在哪个磁道。
 
<4>打印 loading system...
 使用BIOS中断 int 0x10 打印 Loading System...
 
 <5>加载system模块
 加载system的时候为何要使用驱动器的参数信息?
 由于system的扇区数默认是SYSSIZE=0x3000,由于不同的磁盘每一道的扇区数不一样,因此要计算出system所在磁道和扇区等信息。
 
2)setup.s
功能:获取系统数据,并设置IDT和GDT,开A20地址线,进入保护模式。
利用ROM BIOS中断读取机器系统数据,并且这些数据保存到0x90000开始的位置

关于GDTR和IDTR寄存器的说明。
GDTR和IDTR寄存器都是48位的。高32是线性基地址,低16位是GDT或IDT表长度

 <1>读取机器系统数据
 start:
  mov ax,#INITSEG ! 将ds 置成#INITSEG(0x9000)。
  mov ds,ax
  mov ah,#0x03  ! read cursor pos
    ! BIOS 中断0x10 的读光标功能号 ah = 0x03
    ! 输入:bh = 页号
    ! 返回:ch = 扫描开始线,cl = 扫描结束线,
    ! dh = 行号(0x00 是顶端),dl = 列号(0x00 是左边)。
  xor bh,bh
  int 0x10   
  mov [0],dx  ! 将光标位置信息存放在0x90000 处,控制台初始化时会来取。

 ! Get memory size (extended mem, kB)  取扩展内存的大小值(KB)。
 ! 是调用中断0x15,功能号ah = 0x88
 ! 返回:ax = 从0x100000(1M)处开始的扩展内存大小(KB)。
 ! 若出错则CF 置位,ax = 出错码。

  mov ah,#0x88
  int 0x15
  mov [2],ax  ! 将扩展内存数值存在0x90002 处(1 个字)。

 ! Get video-card data:  取显示卡当前显示模式。
 ! 调用BIOS 中断0x10,功能号 ah = 0x0f
 ! 返回:ah = 字符列数,al = 显示模式,bh = 当前显示页。
 ! 0x90004(1 字)存放当前页,0x90006 显示模式,0x90007 字符列数。

  mov ah,#0x0f
  int 0x10
  mov [4],bx  ! bh = display page
  mov [6],ax  ! al = video mode, ah = window width

 ! check for EGA/VGA and some config parameters ! 检查显示方式(EGA/VGA)并取参数。
 ! 调用BIOS 中断0x10,附加功能选择 -取方式信息
 ! 功能号:ah = 0x12,bl = 0x10
 ! 返回:bh = 显示状态
 ! (0x00 - 彩色模式,I/O 端口=0x3dX)
 ! (0x01 - 单色模式,I/O 端口=0x3bX)
 ! bl = 安装的显示内存
 ! (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 = 256k)
 ! cx = 显示卡特性参数(参见程序后的说明)。

  mov ah,#0x12
  mov bl,#0x10
  int 0x10
  mov [8],ax  ! 0x90008 = ??
  mov [10],bx  ! 0x9000A = 安装的显示内存,0x9000B = 显示状态(彩色/单色)
  mov [12],cx  ! 0x9000C = 显示卡特性参数。

 ! Get hd0 data ! 取第一个硬盘的信息(复制硬盘参数表)。
 ! 第1 个硬盘参数表的首地址竟然是中断向量0x41 的向量值!而第2 个硬盘
 ! 参数表紧接第1 个表的后面,中断向量0x46 的向量值也指向这第2 个硬盘
 ! 的参数表首址。表的长度是16 个字节(0x10)。
 ! 下面两段程序分别复制BIOS 有关两个硬盘的参数表,0x90080 处存放第1 个
 ! 硬盘的表,0x90090 处存放第2 个硬盘的表。

  mov ax,#0x0000
  mov ds,ax
  lds si,[4*0x41]   ! 取中断向量0x41 的值,也即hd0 参数表的地址??ds:si
  mov ax,#INITSEG
  mov es,ax
  mov di,#0x0080  ! 传输的目的地址: 0x9000:0x0080 ?? es:di
  mov cx,#0x10   ! 共传输0x10 字节。
  rep
  movsb

 ! Get hd1 data

  mov ax,#0x0000
  mov ds,ax
  lds si,[4*0x46]   ! 取中断向量0x46 的值,也即hd1 参数表的地址??ds:si
  mov ax,#INITSEG
  mov es,ax
  mov di,#0x0090  ! 传输的目的地址: 0x9000:0x0090 ?? es:di
  mov cx,#0x10
  rep
  movsb

 ! Check that there IS a hd1 :-) ! 检查系统是否存在第2 个硬盘,如果不存在则第2 个表清零。
 ! 利用BIOS 中断调用0x13 的取盘类型功能。
 ! 功能号 ah = 0x15;
 ! 输入:dl = 驱动器号(0x8X 是硬盘:0x80 指第1 个硬盘,0x81 第2 个硬盘)
 ! 输出:ah = 类型码;00 --没有这个盘,CF 置位; 01 --是软驱,没有change-line 支持;
 ! 02 --是软驱(或其它可移动设备),有change-line 支持; 03 --是硬盘。

  mov ax,#0x01500
  mov dl,#0x81
  int 0x13
  jc no_disk1
  cmp ah,#3   ! 是硬盘吗?(类型 = 3 ?)。
  je is_disk1

 <2>移动system模块到0x0000处
 do_move:
  mov es,ax   ! destination segment  ! es:di -> 目的地址(初始为0x0000:0x0)
  add ax,#0x1000
  cmp ax,#0x9000   ! 已经把从0x8000 段开始的64k 代码移动完?
  jz end_move
  mov ds,ax   ! source segment ! ds:si -> 源地址(初始为0x1000:0x0)
  sub di,di
  sub si,si
  mov cx,#0x8000   ! 移动0x8000 字(64k 字节)。
  rep
  movsw
  jmp do_move

 <3>设置IDT和GDT,开A20地址线.
 将system移动到0x0000之后变设置IDT和GDT,开启保护模式.
 end_move:
  mov ax,#SETUPSEG  ! right, forgot this at first. didn't work :-)
  mov ds,ax   ! ds 指向本程序(setup)段。
  lidt idt_48   ! load idt with 0,0
     ! 加载中断描述符表(idt)寄存器,idt_48 是6 字节操作数的位置。前2 字节表示idt 表的限长,后4 字节表示idt 表所处的基地址。
  lgdt gdt_48   ! load gdt with whatever appropriate
     ! 加载全局描述符表(gdt)寄存器,gdt_48 是6 字节操作数的位置。
 ! that was painless, now we enable A20
 ! 以上的操作很简单,现在我们开启A20 地址线。参见程序列表后有关A20 信号线的说明。
 ! 关于所涉及到的一些端口和命令,可参考kernel/chr_drv/keyboard.S 程序后对键盘接口的说明。

  call empty_8042  ! 等待输入缓冲器空。
     ! 只有当输入缓冲器为空时才可以对其进行写命令。
  mov al,#0xD1   ! command write ! 0xD1 命令码-表示要写数据到
  out #0x64,al   ! 8042 的P2 端口。P2 端口的位1 用于A20 线的选通。数据要写到0x60 口。
  call empty_8042  ! 等待输入缓冲器空,看命令是否被接受。
  mov al,#0xDF   ! A20 on  ! 选通A20 地址线的参数。
  out #0x60,al
  call empty_8042  ! 输入缓冲器为空,则表示A20 线已经选通。

3)head.s
功能:重新设置IDT和GDT信息,使用setup_idt和setup_gdt来重设IDT和GDT。
 
 <1>setup_idt函数重置IDT表

 ----idt描述符----
 .align 2    # 按4 字节方式对齐内存地址边界。
 idt_descr:    #下面两行是lidt 指令的6 字节操作数:长度,基址。
  .word 256*8-1   # idt contains 256 entries idt表的长度256项(16位)
  .long idt              # idt 表的基地址(32位)

 ----中断向量表----
 .align 3    # 按8 字节方式对齐内存地址边界。
 idt:  .fill 256,8,0   # idt is uninitialized # 256 项,每项8 字节,填0。

 ----setup_idt函数----
 setup_idt:
  lea ignore_int,%edx  # 将ignore_int 的有效地址(偏移值)值-> edx 寄存器
  movl $0x00080000,%eax  # 将选择符0x0008 置入eax 的高16 位中。
  movw %dx,%ax  /* selector = 0x0008 = cs */
     # 偏移值的低16 位置入eax 的低16 位中。此时eax 含有
     #门描述符低4 字节的值。
  movw $0x8E00,%dx  /* interrupt gate - dpl=0, present */
     # 此时edx 含有门描述符高4 字节的值。
  lea idt,%edi   # idt 是中断描述符表的地址。
  mov $256,%ecx  # idt一共256项。都设置为ignore_int函数。
 rp_sidt:
  movl %eax,(%edi)  # 将哑中断门描述符存入表中。
  movl %edx,4(%edi) # 将ignore_int设置给idt。
  addl $8,%edi   # edi 指向表中下一项。
  dec %ecx
  jne rp_sidt
  lidt idt_descr   # 加载中断描述符表寄存器值。
  ret

 <2>setup_gdt 函数重置GDT表

 ----gdt描述符----
 .align 2
 gdt_descr:   # 下面两行是lgdt 指令的6 字节操作数:长度,基址。
  .word 256*8-1  # so does gdt (not that that's any # not ?? note.
  .long gdt           # magic number, but it works for me :^)
 
 ----gdt表----
 # 全局表。前4 项分别是空项(不用)、代码段描述符、数据段描述符、系统段描述符,其中
 # 系统段描述符linux 没有派用处。后面还预留了252 项的空间,用于放置所创建任务的
 # 局部描述符(LDT)和对应的任务状态段TSS 的描述符。
 # (0-nul, 1-cs, 2-ds, 3-sys, 4-TSS0, 5-LDT0, 6-TSS1, 7-LDT1, 8-TSS2 etc...)
 .align 3     # 按8 字节方式对齐内存地址边界。
 gdt:  .quad 0x0000000000000000  /* NULL descriptor */
  .quad 0x00c09a0000000fff  /* 16Mb */ # 0x08,内核代码段最大长度16M。
  .quad 0x00c0920000000fff  /* 16Mb */ # 0x10,内核数据段最大长度16M。
  .quad 0x0000000000000000  /* TEMPORARY - don't use */
  .fill 252,8,0    /* space for LDT's and TSS's etc */

 ----setup_gdt函数----
 setup_gdt:
  lgdt gdt_descr   # 加载全局描述符表寄存器(内容已设置好,见234-238 行)。
  ret

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值