首页 > 代码库 > 一,内核加载程序

一,内核加载程序

加载程序不是内核的重点,我们只需知道内核被加载到什么地方,内核的入口地址是什么即可,一些技术细节没必要过分关心。

xv6的内核加载程序由两个文件组成:bootasm.S和bootmain.c

bootasm.s的主要目的就是由实模式进入保护模式,下面是bootasm.s的代码注释:

 1 #加载器被BIOS加载到0x7c00地址处,此时cs = 0, eip = 7c00。其他寄存器的值 2 #不关心 3  4 #include "asm.h" 5 #include "mmu.h" 6 .code16 7 .global start 8 start: 9     cli10 11     xorw    %ax, %ax12     movw    %ax, %ds13     movw    %ax, %es14     movw    %ax, %ss15 16 #打开A20总线,这是个历史遗留问题17 seta20.1:18     inb     $0x64,%al19     testb   $0x2,%al20     jnz     seta20.121 22     movb    $0xd1,%al23     outb    %al,$0x6424 25 seta20.2:26     inb     $0x64,%al27     testb   $0x2,%al28     jnz     seta20.229 30     movb    $0xdf,%al31     outb    %al,$0x6032 33 #进入保护模式34     lgdt    gdtdesc35     movl    %cr0, %eax36     orl     $CR0_PE, %eax37     movl    %eax, %cr038 39     ljmp    $(SEG_KCODE<<3), $start3240 41 #保护模式代码42 .code3243 start32:44 #设置段寄存器的值 45     movw    $(SEG_KDATA<<3), %ax46     movw    %ax, %ds47     movw    %ax, %es48     movw    %ax, %ss49     movw    $0, %ax50     movw    %ax, %fs51     movw    $ax, %gs52 53 #设置栈指针54     movl    $start, %esp55     call    bootmain56 57 #不应该从bootmain函数中返回,如果返回了,进入无限循环58 59 spin:60     jmp     spin61 #加载器使用的GDT62 .p2align    263 gdt:64     SEG_NULLASM65     SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)   #代码段 66     SEG_ASM(STA_W, 0x0, 0xffffffff)         #数据段67 68 gdtdesc:69     .word   (gdtdesc - gdt - 1)70     .long   gdt

 

bootmain.c主要是把内核可执行文件加载到内存中,xv6内核的可执行文件是elf格式,所以要对elf文件格式有所了解。

这里只用到elf文件格式的两个表头,只需了解这两个表头的含义即可。

第一个表头是elf头部结构,位于elf文件的首部

#define EI_NIDENT 16typedef struct{    unsigned char e_ident[EI_NIDENT];    //目标文件标识信息    Elf32_Half e_type;                             //目标文件类型    Elf32_Half e_machine;                       //目标体系结构类型    Elf32_Word e_version;                      //目标文件版本    Elf32_Addr e_entry;                          //程序入口的虚拟地址,若没有,可为0    Elf32_Off e_phoff;                            //程序头部表格(Program Header Table)的偏移量(按字节计算),若没有,可为0    Elf32_Off e_shoff;                            //节区头部表格(Section Header Table)的偏移量(按字节计算),若没有,可为0    Elf32_Word e_flags;                        //保存与文件相关的,特定于处理器的标志。标志名称采用 EF_machine_flag的格式。    Elf32_Half e_ehsize;                        //ELF 头部的大小(以字节计算)。    Elf32_Half e_phentsize;                   //程序头部表格的表项大小(按字节计算)。    Elf32_Half e_phnum;                      //程序头部表格的表项数目。可以为 0。    Elf32_Half e_shentsize;                  //节区头部表格的表项大小(按字节计算)。    Elf32_Half e_shnum;      //节区头部表格的表项数目。可以为 0。    Elf32_Half e_shstrndx;  //节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节区名称字符串表,此参数可以为 SHN_UNDEF。}Elf32_Ehdr;

我们只用到这个结构中的两项:e_phoff(程序头部表格的偏移量)和 e_phnum(程序头部表格的表项数目)。

 

第二个结构是程序头部,指示该elf文件的哪些部分需要被加载到内存中去,一个程序头部结构对应一个要被加载入内存的文件段。

typedef struct {    Elf32_Word p_type;    //段类型    Elf32_Off p_offset;      //段位置    Elf32_Addr p_vaddr;    //给出段的第一个字节将被放到内存中的虚拟地址    Elf32_Addr p_paddr;    //仅用于与物理地址相关的系统中    Elf32_Word p_filesz;     //给出段在文件映像中所占的字节数    Elf32_Word p_memsz;    //给出段在内存映像中占用的字节数    Elf32_Word p_flags;    //与段相关的标志    Elf32_Word p_align;    //对齐} Elf32_phdr;

该结构中的p_offset指明该段在文件中的偏移,p_filesz指明该段的大小,p_paddr指明要被加载进的物理地址,有了这三个信息,就可以加载内核可执行文件了。

 

bootmain.c所做的工作就是根据以上两个结构的内容把内核可执行文件的某些部分加载进指定的物理内存处。

最后跳到内核的入口处,开始执行内核代码,内核被加载成功。

一,内核加载程序