• 设置工作模式与环境(中):建造二级引导器


    1)上节课安装的GRUB不是已经 把我们的操作 系统加载到内存中了吗?为什么还要二级引导器?

    • 二级引导器是操作系统的排头兵,他先去收集计算机的信息,看看计算机硬件支持不支持运行我们的操作系统。GRUB负责的是操作系统加载进内存,而二级引导器负责的是检验计算机 能不能运行我们的操作系统,并且初始化好一些硬件(cpu,显卡,内存)的配置,把内核相关的文件放到正确的位置上。二者的工作使命是不一样的。

    2)二级引导器先去收集了计算机的硬件信息,那么这个硬件信息以何种方式,在哪个位置让我的操作系统知道呢?

    • 信息的话我们设计 一个数据结构来存放,地址的话放在内存1MB的地方

       typedef struct s_MACHBSTART
       {
           u64_t   mb_krlinitstack;//内核栈地址
           u64_t   mb_krlitstacksz;//内核栈大小
           u64_t   mb_imgpadr;//操作系统映像
           u64_t   mb_imgsz;//操作系统映像大小
           u64_t   mb_bfontpadr;//操作系统字体地址
           u64_t   mb_bfontsz;//操作系统字体大小
           u64_t   mb_fvrmphyadr;//机器显存地址
           u64_t   mb_fvrmsz;//机器显存大小
           u64_t   mb_cpumode;//机器CPU工作模式
           u64_t   mb_memsz;//机器内存大小
           u64_t   mb_e820padr;//机器e820数组地址
           u64_t   mb_e820nr;//机器e820数组元素个数
           u64_t   mb_e820sz;//机器e820数组大小
           //……
           u64_t   mb_pml4padr;//机器页表数据地址
           u64_t   mb_subpageslen;//机器页表个数
           u64_t   mb_kpmapphymemsz;//操作系统映射空间大小
           //……
           graph_t mb_ghparm;//图形信息
       }__attribute__((packed)) machbstart_t;

    3)二级引导器的功能文件我们怎样设计的?

    4)上面的文件我们需要编译才能使用,编译过程是怎样的?

    把三个文件打包成映像文件

     lmoskrlimg -m k -lhf initldrimh.bin -o Cosmos.eki -f initldrkrl.bin initldrsve.bin

    5)现在二级引导器文件虽然有了,那GRUB如何找到他呢?

    • 写一个GRUB头,这里分两个子文件

      • imginithead.asm 汇编文件,让 GRUB 识别,同时设置 C 语言运行环境,用于调用 C 函数

      • inithead.c 文件,找到二级引导器的核心文件——initldrkrl.bin,并且把它放置到特定的内存地址上。

    6)imginithead.asm具体代码是怎样实现的?

    • 首先是 GRUB1 和 GRUB2 需要的两个头结构

       
       MBT_HDR_FLAGS EQU 0x00010003
       MBT_HDR_MAGIC EQU 0x1BADB002
       MBT2_MAGIC EQU 0xe85250d6
       global _start
       extern inithead_entry
       [section .text]
       [bits 32]
       _start:
        jmp _entry
       align 4
       mbt_hdr:
        dd MBT_HDR_MAGIC
        dd MBT_HDR_FLAGS
        dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
        dd mbt_hdr
        dd _start
        dd 0
        dd 0
        dd _entry
       ALIGN 8
       mbhdr:
        DD 0xE85250D6
        DD 0
        DD mhdrend - mbhdr
        DD -(0xE85250D6 + 0 + (mhdrend - mbhdr))
        DW 2, 0
        DD 24
        DD mbhdr
        DD _start
        DD 0
        DD 0
        DW 3, 0
        DD 12
        DD _entry
        DD 0  
        DW 0, 0
        DD 8
       mhdrend:
    • 然后是关中断并加载 GDT

       
       _entry:
        cli           ;关中断
        in al, 0x70
        or al, 0x80  
        out 0x70,al ;关掉不可屏蔽中断  
        lgdt [GDT_PTR] ;加载GDT地址到GDTR寄存器
        jmp dword 0x8 :_32bits_mode ;长跳转刷新CS影子寄存器
        ;………………
       ;GDT全局段描述符表
       GDT_START:
       knull_dsc: dq 0
       kcode_dsc: dq 0x00cf9e000000ffff
       kdata_dsc: dq 0x00cf92000000ffff
       k16cd_dsc: dq 0x00009e000000ffff ;16位代码段描述符
       k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
       GDT_END:
       GDT_PTR:
       GDTLEN dw GDT_END-GDT_START-1 ;GDT界限
       GDTBASE dd GDT_START
    • 最后是初始化段寄存器和通用寄存器、栈寄存器,这是为了给调用 inithead_entry 这个 C 函数做准备

       
       _32bits_mode:
        mov ax, 0x10
        mov ds, ax
        mov ss, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        xor eax,eax
        xor ebx,ebx
        xor ecx,ecx
        xor edx,edx
        xor edi,edi
        xor esi,esi
        xor ebp,ebp
        xor esp,esp
        mov esp,0x7c00 ;设置栈顶为0x7c00
        call inithead_entry ;调用inithead_entry函数在inithead.c中实现
        jmp 0x200000 ;跳转到0x200000地址

    7)上述代码的最后调用了 inithead_entry 函数,这个函数 在哪里呢?

    • 在 inithead.c 中

       
       #define MDC_ENDGIC 0xaaffaaffaaffaaff
       #define MDC_RVGIC 0xffaaffaaffaaffaa
       #define REALDRV_PHYADR 0x1000
       #define IMGFILE_PHYADR 0x4000000
       #define IMGKRNL_PHYADR 0x2000000
       #define LDRFILEADR IMGFILE_PHYADR
       #define MLOSDSC_OFF (0x1000)
       #define MRDDSC_ADR (mlosrddsc_t*)(LDRFILEADR+0x1000)
       
       void inithead_entry()
       {
           write_realintsvefile();
           write_ldrkrlfile();
           return;
       }
       //写initldrsve.bin文件到特定的内存中
       void write_realintsvefile()
       {
           fhdsc_t *fhdscstart = find_file("initldrsve.bin");
           if (fhdscstart == NULL)
          {
               error("not file initldrsve.bin");
          }
           m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
                  (void *)REALDRV_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
           return;
       }
       //写initldrkrl.bin文件到特定的内存中
       void write_ldrkrlfile()
       {
           fhdsc_t *fhdscstart = find_file("initldrkrl.bin");
           if (fhdscstart == NULL)
          {
               error("not file initldrkrl.bin");
          }
           m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
                  (void *)ILDRKRL_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
           return;
       }
       //在映像文件中查找对应的文件
       fhdsc_t *find_file(char_t *fname)
       {
           mlosrddsc_t *mrddadrs = MRDDSC_ADR;
           if (mrddadrs->mdc_endgic != MDC_ENDGIC ||
               mrddadrs->mdc_rv != MDC_RVGIC ||
               mrddadrs->mdc_fhdnr < 2 ||
               mrddadrs->mdc_filnr < 2)
          {
               error("no mrddsc");
          }
           s64_t rethn = -1;
           fhdsc_t *fhdscstart = (fhdsc_t *)((u32_t)(mrddadrs->mdc_fhdbk_s) + LDRFILEADR);
           for (u64_t i = 0; i < mrddadrs->mdc_fhdnr; i++)
          {
               if (strcmpl(fname, fhdscstart[i].fhd_name) == 0)
              {
                   rethn = (s64_t)i;
                   goto ok_l;
              }
          }
           rethn = -1;
       ok_l:
           if (rethn < 0)
          {
               error("not find file");
          }
           return &fhdscstart[rethn];
       }

      我们实现了 inithead_entry 函数,它主要干了两件事,即分别调用 write_realintsvefile();、write_ldrkrlfile() 函数,把映像文件中的 initldrsve.bin 文件和 initldrkrl.bin 文件写入到特定的内存地址空间中,具体地址在上面代码中的宏有详细定义。

    8)前面我们的imghead.asm 汇编文件代码中,我们的最后一条指令是“jmp 0x200000”,那么它是跳到哪里去了 ?

    • 其实就是 我们下面写 的C文件中把二级引导器核心文件放置的位置。这一跳就直接 进入二级引导器的主模块了。

    9)现在我们跳到二级引导器的主模块了,那对应的CPU啊 ,寄存器啊这类的环境应该也要做相应的改变,代码应该是怎样的?

     
     _entry:
      cli
      lgdt [GDT_PTR];加载GDT地址到GDTR寄存器
      lidt [IDT_PTR];加载IDT地址到IDTR寄存器
      jmp dword 0x8 :_32bits_mode;长跳转刷新CS影子寄存器
     _32bits_mode:
      mov ax, 0x10 ; 数据段选择子(目的)
      mov ds, ax
      mov ss, ax
      mov es, ax
      mov fs, ax
      mov gs, ax
      xor eax,eax
      xor ebx,ebx
      xor ecx,ecx
      xor edx,edx
      xor edi,edi
      xor esi,esi
      xor ebp,ebp
      xor esp,esp
      mov esp,0x90000 ;使得栈底指向了0x90000
      call ldrkrl_entry ;调用ldrkrl_entry函数
      xor ebx,ebx
      jmp 0x2000000 ;跳转到0x2000000的内存地址
      jmp $
     GDT_START:
     knull_dsc: dq 0
     kcode_dsc: dq 0x00cf9a000000ffff ;a-e
     kdata_dsc: dq 0x00cf92000000ffff
     k16cd_dsc: dq 0x00009a000000ffff ;16位代码段描述符
     k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
     GDT_END:
     GDT_PTR:
     GDTLEN dw GDT_END-GDT_START-1 ;GDT界限
     GDTBASE dd GDT_START
     
     IDT_PTR:
     IDTLEN dw 0x3ff
     IDTBAS dd 0 ;这是BIOS中断表的地址和长度

    10)接下来我们要获取内存布局信息,还要设置显卡的工作模式,这些都是需要BIOS提供的中断服务才能去完成的,可是我们在C函数中要想直接调用中断是不可能的,因为C的CPU工作环境是32位保护模式,中断的工作环境是16位的实模式。这该如何调用呢?

    • 我们的C正在工作,然后被中断了,所以我们要把C语言环境下的CPU上下文,也就是保护模式下的一些寄存器啥的保存到我们的内存中。

    • 现在我们可以切换到实模式了,我们调用BIOS中断,设置好我们的显卡,内存啊相关的配置,然后把他们保存到内存中。

    • 最后我们切换回保护模式,重新把我们寄存器的信息恢复,那我们的环境就是C环境了。

    11)上面的三个流程用代码是怎样实现的?

     realadr_call_entry:
      pushad     ;保存通用寄存器
      push   ds
      push   es
      push   fs ;保存4个段寄存器
      push   gs
      call save_eip_jmp ;调用save_eip_jmp
      pop gs
      pop fs
      pop es     ;恢复4个段寄存器
      pop ds
      popad       ;恢复通用寄存器
      ret
     save_eip_jmp:
      pop esi ;弹出call save_eip_jmp时保存的eip到esi寄存器中,
      mov [PM32_EIP_OFF],esi ;把eip保存到特定的内存空间中
      mov [PM32_ESP_OFF],esp ;把esp保存到特定的内存空间中
      jmp dword far [cpmty_mode];长跳转这里表示把cpmty_mode处的第一个4字节装入eip,把其后的2字节装入cs
     cpmty_mode:
      dd 0x1000
      dw 0x18
      jmp $

    12)现在还差二级引导器的主函数,长什么样?

     
     void ldrkrl_entry()
     {
         init_bstartparm();
         return;
     }
    • init_bstartparm()就是收集计算机信息的函数,下节内容我们再去编写他。

    •  

  • 相关阅读:
    C#获取htmltry
    分页存储过程的写法 可以被调用
    验证邮箱地址是否正确的方法
    WinForm 异步调用方法
    C#基础复习第一部
    PowerDesigner12.5 下载、汉化及破解
    C# Timer用法及实例详解
    ADO.NET复习笔记
    C# 使用委托实现多线程调用窗体
    C#中存储过程的调用
  • 原文地址:https://www.cnblogs.com/YXBLOGXYY/p/16077977.html
Copyright © 2020-2023  润新知