yale_OS(6)——xv6中boot loader的学习

xv6的引导程序

当x86的PC启动的时候,它首先执行的程序是BIOS,该段程序存储在主板的flash内存中,

BIOS的任务有以下两种:

1.为后面程序的运行初始化硬件

2.把控制权转移到操作系统,尤其是转移到从boot sector中读取的代码处,该boot  sector是boot disk的第一个512字节的sector,


BIOS加载扇区的内容到内存的0x7c00处,然后跳转到该地址(设置处理的ip值)处。


当boot sector开始执行的时候,处理器是运行在Intel 的8088的模式下(实模式),

xv6的boot sector的任务如下:

1.让处理器运行到更加现代的操作模式下,

2.从磁盘上加载xv6的内核,然后转移控制权到内核处。


在xv6中,boot sector有两个源代码文件组成,一个是用汇编语言编写的(16-bit和32-bit的x86汇编)代码bootasm.S,另一是用C语言编写的代码bootmain.c

下面介绍boot sector的操作过程,从BIOS执行开始,直到转移控制权到内核处。

boot sector可以看着是内核自身的一个缩影,它包含低级的汇编代码和C代码,它管理着自己的内存,它甚至含有一个设备驱动程序,而所有的这一切都在512字节的机器代码中。

以下分别对bootasm.S和bootmain.c文件中的代码进行分析

1.bootasm.S中的代码如下(主要完成处理器从实模式转换到32位的保护模式):

  1 #include "asm.h"
  2 
  3 # Start the first CPU: switch to 32-bit protected mode, jump into C.
  4 # The BIOS loads this code from the first sector of the hard disk into
  5 # memory at physical address 0x7c00 and starts executing in real mode
  6 # with %cs=0 %ip=7c00.
  7 
  8 #define SEG_KCODE 1  // kernel code
  9 #define SEG_KDATA 2  // kernel data+stack
 10 
 11 #define CR0_PE    1  // protected mode enable bit
 12 
 13 .code16                       # Assemble for 16-bit mode
 14 .globl start
 15 start:
 16   cli                         # Disable interrupts
 17 
 18   # Set up the important data segment registers (DS, ES, SS).
 19   xorw    %ax,%ax             # Segment number zero
 20   movw    %ax,%ds             # -> Data Segment
 21   movw    %ax,%es             # -> Extra Segment
 22   movw    %ax,%ss             # -> Stack Segment
 23 
 24   # Enable A20:
 25   #   For backwards compatibility with the earliest PCs, physical
 26   #   address line 20 is tied low, so that addresses higher than
 27   #   1MB wrap around to zero by default.  This code undoes this.
 28 seta20.1:
 29   inb     $0x64,%al               # Wait for not busy
 30   testb   $0x2,%al
 31   jnz     seta20.1
 32 
 33   movb    $0xd1,%al               # 0xd1 -> port 0x64
 34   outb    %al,$0x64
 35 
 36 seta20.2:
 37   inb     $0x64,%al               # Wait for not busy
 38   testb   $0x2,%al
 39   jnz     seta20.2
 40 
 41   movb    $0xdf,%al               # 0xdf -> port 0x60
 42   outb    %al,$0x60
 43 
 44   # Switch from real to protected mode, using a bootstrap GDT
 45   # and segment translation that makes virtual addresses 
 46   # identical to physical addresses, so that the 
 47   # effective memory map does not change during the switch.
 48   lgdt    gdtdesc
 49   movl    %cr0, %eax
 50   orl     $CR0_PE, %eax
 51   movl    %eax, %cr0
 52 
 53   # This ljmp is how you load the CS (Code Segment) register.
 54   # SEG_ASM produces segment descriptors with the 32-bit mode
 55   # flag set (the D flag), so addresses and word operands will
 56   # default to 32 bits after this jump.
 57   ljmp  $(SEG_KCODE<<3), $start32
 58  

 59 .code32                       # Assemble for 32-bit mode
 60 start32:
 61   # Set up the protected-mode data segment registers
 62   movw    $(SEG_KDATA<<3), %ax    # Our data segment selector
 63   movw    %ax, %ds                # -> DS: Data Segment
 64   movw    %ax, %es                # -> ES: Extra Segment
 65   movw    %ax, %ss                # -> SS: Stack Segment
 66   movw    $0, %ax                 # Zero segments not ready for use
 67   movw    %ax, %fs                # -> FS
 68   movw    %ax, %gs                # -> GS
 69 
 70   # Set up the stack pointer and call into C.
 71   movl    $start, %esp
 72   call    bootmain
 73 
 74   # If bootmain returns (it shouldn't), trigger a Bochs
 75   # breakpoint if running under Bochs, then loop.
 76   movw    $0x8a00, %ax            # 0x8a00 -> port 0x8a00
 77   movw    %ax, %dx
 78   outw    %ax, %dx
 79   movw    $0x8ae0, %ax            # 0x8ae0 -> port 0x8a00
 80   outw    %ax, %dx
 81 spin:
 82   jmp     spin
 83 
 84 # Bootstrap GDT
 85 .p2align 2                                # force 4 byte alignment
 86 gdt:
 87   SEG_NULLASM                             # null seg
 88   SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)   # code seg
 89   SEG_ASM(STA_W, 0x0, 0xffffffff)         # data seg
 90 
 91 gdtdesc:
 92   .word   (gdtdesc - gdt - 1)             # sizeof(gdt) - 1
 93   .long   gdt                             # address gdt

bootsector中第一条指令为cli(1行),用于禁止处理器的中断,中断是一种用于硬件设备调用操作系统中断处理程序的方式,BIOS可以看作一个小型的操作系统,因此其有它自己的中断处理程序,作为硬件初始化的一部分,但是boot sector运行的时候,BIOS已不再运行了,且此时不适合处理硬件设备的中断。当xv6内核准备就绪后,将会重新启动中断。由于当BIOS运行完后,我们并不知道ds,es,ss里存储的是什么内容,因此,我们需要做的是设置寄存器中的值为0,然后分别把ax中的值copy到ds,es,ss(19~22行)。

 

lgdt gdtdesc指令(48行):将GDT表的首地址加载到GDTR中,(49~51行):将cr0寄存器的最低位设置为1,标志着系统进入保护模式,


ljmp指令(57行):让系统开始使用23位的寻址模式,该指令是在系统进入保护模式后执行的,因此,$(SEG_KCODE << 3)会被存入寄存器cs中,代表的是段选择子,从GDT表的定义可以看到基地址为0x0,而偏移地址为:$start32,$start32实际上表示的是接下来指令的链接地址,也就是可执行程序在内存中的虚拟地址,只是刚好在这里编译生成的可执行程序boot的加载地址与链接地址是一致的。因此,可以跳转成功。关于链接地址与加载地址可参考邵志远老师的32位操作系统实践课程


进入保护模式后,程序就重新对段寄存器进行了初始化,并且对堆栈指针进行了赋值,然后便调用bootmain函数。下面对bootmain.c进行分析

2.bootmain.c源代码如下(完成将内核的可执行代码从硬盘中读入到内存):

// Boot loader.
// 
// Part of the boot sector, along with bootasm.S, which calls bootmain().
// bootasm.S has put the processor into protected 32-bit mode.
// bootmain() loads an ELF kernel image from the disk starting at
// sector 1 and then jumps to the kernel entry routine.

#include "types.h"
#include "elf.h"
#include "x86.h"

#define SECTSIZE  512

void readseg(uchar*, uint, uint);

void
bootmain(void)
{
  struct elfhdr *elf;
  struct proghdr *ph, *eph;
  void (*entry)(void);
  uchar* va;

  elf = (struct elfhdr*)0x10000;  // scratch space

  // Read 1st page off disk
  readseg((uchar*)elf, 4096, 0);

  // Is this an ELF executable?
  if(elf->magic != ELF_MAGIC)
    return;  // let bootasm.S handle error

  // Load each program segment (ignores ph flags).
  ph = (struct proghdr*)((uchar*)elf + elf->phoff);
  eph = ph + elf->phnum;
  for(; ph < eph; ph++) {
    va = (uchar*)(ph->va & 0xFFFFFF);
    readseg(va, ph->filesz, ph->offset);
    if(ph->memsz > ph->filesz)
      stosb(va + ph->filesz, 0, ph->memsz - ph->filesz);
  }

  // Call the entry point from the ELF header.
  // Does not return!
  entry = (void(*)(void))(elf->entry & 0xFFFFFF);
  entry();
}

void
waitdisk(void)
{
  // Wait for disk ready.
  while((inb(0x1F7) & 0xC0) != 0x40)
    ;
}

// Read a single sector at offset into dst.
void
readsect(void *dst, uint offset)
{
  // Issue command.
  waitdisk();
  outb(0x1F2, 1);   // count = 1
  outb(0x1F3, offset);
  outb(0x1F4, offset >> 8);
  outb(0x1F5, offset >> 16);
  outb(0x1F6, (offset >> 24) | 0xE0);
  outb(0x1F7, 0x20);  // cmd 0x20 - read sectors

  // Read data.
  waitdisk();
  insl(0x1F0, dst, SECTSIZE/4);
}

// Read 'count' bytes at 'offset' from kernel into virtual address 'va'.
// Might copy more than asked.
void
readseg(uchar* va, uint count, uint offset)
{
  uchar* eva;

  eva = va + count;

  // Round down to sector boundary.
  va -= offset % SECTSIZE;

  // Translate from bytes to sectors; kernel starts at sector 1.
  offset = (offset / SECTSIZE) + 1;

  // If this is too slow, we could read lots of sectors at a time.
  // We'd write more to memory than asked, but it doesn't matter --
  // we load in increasing order.
  for(; va < eva; va += SECTSIZE, offset++)
    readsect(va, offset);
}

待续。。。。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值