Linux 下不经过BIOS重启(i386)


有个项目,要求在Linux下不经过BIOS重启,i386平台。

一、可行性分析

  众所周知,BIOS中包含了CPU及其他各种设备的初始化代码,Linux系统运行之后是否能够将各种用到的设备返回到刚被BIOS初始化后的状态是是否可行的关键。

  从项目的条件来看,外设并不是问题。因为要首先开起来的那个Linux只会用到磁盘系统。而通用的磁盘系统是不存在与启动相关的关键状态的。

  另外就是核心系统(CPU、内存初始化数据分布等)。CPU的状态时可以设置的,因此问题貌似也不大,将CPU返回实模式即可。内存中的BIOS数据也不会被Linux改动,因此也不会有问题。

二、Linux如何重启x86系统

  查阅Linux内核(2.6.33)中i386的关机代码(arch/x86/kernel/reboot.c),该文件与重启相关的关键点有三个(按代码的先后顺序):第一个是static int __init reboot_setup(char *str)函数和__setup("reboot=", reboot_setup);宏,这是在Linux启动时通过内核参数reboot=设置启动方式,记录到reboot_type变量中,默认为BOOT_KBD,即键盘启动。第二个是从static const unsigned long long real_mode_gdt_entries [3] = ... 一直到 void machine_real_restart(const unsigned char *code, int length)函数,这是专门为x86系统设计的不通过电源系统快速重启(直接跳到BIOS中重启)。第三个是static void native_machine_emergency_restart(void)函数,这是关机重启的最后阶段,且与前面的reboot_type呼应。要注意的是系统执行到这儿已经关闭了诸如中断控制器、重置了时钟、关闭了所有AP并确保接下来的代码都在唯一的BSP上执行。

  首先将第一和第三点。第一点是启动时的reboot_type设置,它影响到了第三点中实际restart操作的行为。native_machine_emergency_restart函数是重启过程中最后的步骤,i386系统重启最后都会走到这里来。这个函数的结构是一个死循环中包含一个switch(reboot_type)分支结构,如果reboot_type选定的那种重启方式执行失败了(正常情况下,这里调用的函数如果成功就不会返回了,直接导致系统重启。如果失败就会返回),那么就把reboot_type设置为默认的BOOT_KBD,再来重启一次。

  键盘方式看来是最稳妥最原始的重启方式,它的步骤是这样的:

  1. for (i = 0; i < 10; i++) {   
  2.     kb_wait();   
  3.     udelay(50);   
  4.     outb(0xfe, 0x64); /* pulse reset low */  
  5.     udelay(50);   
  6. }  

0x64端口是i8042键盘控制器的控制端口,0xfe命令字的意思是将P32-P21三个针脚拉为低电平,持续6usec。这段代码的实际效果就相当于你按下机箱上的 RESET 键。

  在那些重启方式中,还有一种方式是BOOT_BIOS,调用的就是第二个关键点中的machine_real_restart函数,它将CPU返回到实模式,然后跳到CPU上电后的那个地址(FFFF:0000),BIOS会在这个地址处放一个jump,跳到BIOS真正的开始处。

  显然我就可以直接拿这个函数开刀,把它改造成项目所需要的样子,如此一来,省去了再去写代码进行实模式切换的麻烦,直接用现成的。

三、分析和改造machine_real_restart函数

  为了尽量少更改原有的代码,另开了一个文件,将machine_real_restart函数和相关的结构体拷贝过来,然后慢慢改。当然,这个reboot文件也是要改的,就是再增加一种启动方式,我将它命名为BOOT_MBR,在第一点和第三点相关的地方增加一种启动方式即可。

  接下来就是着手分析改造了。首先看这部分功能包含哪些东西:

  1. static const unsigned long long  
  2. real_mode_gdt_entries [3] =   
  3. {   
  4.     0x0000000000000000ULL,  /* Null descriptor */  
  5.     0x00009b000000ffffULL,  /* 16-bit real-mode 64k code at 0x00000000 */  
  6.     0x000093000100ffffULL   /* 16-bit real-mode 64k data at 0x00000100 */  
  7. };   
  8. static const struct desc_ptr   
  9. real_mode_gdt = { sizeof (real_mode_gdt_entries) - 1, (long)real_mode_gdt_entries },   
  10. real_mode_idt = { 0x3ff, 0 };   
  11. static const unsigned char real_mode_switch [] =   
  12. {   
  13.     0x66, 0x0f, 0x20, 0xc0,         /*    movl  %cr0,%eax        */  
  14.     0x66, 0x83, 0xe0, 0x11,         /*    andl  $0x00000011,%eax */  
  15.     0x66, 0x0d, 0x00, 0x00, 0x00, 0x60, /*    orl   $0x60000000,%eax */  
  16.     0x66, 0x0f, 0x22, 0xc0,         /*    movl  %eax,%cr0        */  
  17.     0x66, 0x0f, 0x22, 0xd8,         /*    movl  %eax,%cr3        */  
  18.     0x66, 0x0f, 0x20, 0xc3,         /*    movl  %cr0,%ebx        */  
  19.     0x66, 0x81, 0xe3, 0x00, 0x00, 0x00, 0x60,   /*    andl  $0x60000000,%ebx */  
  20.     0x74, 0x02,             /*    jz    f                */  
  21.     0x0f, 0x09,             /*    wbinvd                 */  
  22.     0x24, 0x10,             /* f: andb  $0x10,al         */  
  23.     0x66, 0x0f, 0x22, 0xc0          /*    movl  %eax,%cr0        */  
  24. };   
  25. static const unsigned char jump_to_bios [] =   
  26. {   
  27.     0xea, 0x00, 0x00, 0xff, 0xff        /*    ljmp  $0xffff,$0x0000  */  
  28. };   
  29. void machine_real_restart(const unsigned char *code, int length)   
  30. {   
  31.     local_irq_disable();   
  32.        
  33.     spin_lock(&rtc_lock);   
  34.     CMOS_WRITE(0x00, 0x8f);   
  35.     spin_unlock(&rtc_lock);   
  36.        
  37.     memcpy(swapper_pg_dir, swapper_pg_dir + KERNEL_PGD_BOUNDARY,   
  38.         sizeof(swapper_pg_dir [0]) * KERNEL_PGD_PTRS);   
  39.        
  40.     load_cr3(swapper_pg_dir);   
  41.        
  42.     *((unsigned short *)0x472) = reboot_mode;   
  43.        
  44.     memcpy((void *)(0x1000 - sizeof(real_mode_switch) - 100),   
  45.         real_mode_switch, sizeof (real_mode_switch));   
  46.     memcpy((void *)(0x1000 - 100), code, length);   
  47.        
  48.     load_idt(&real_mode_idt);   
  49.     load_gdt(&real_mode_gdt);   
  50.        
  51.     __asm__ __volatile__ ("movl $0x0010,%%eax\n"  
  52.                 "\tmovl %%eax,%%ds\n"  
  53.                 "\tmovl %%eax,%%es\n"  
  54.                 "\tmovl %%eax,%%fs\n"  
  55.                 "\tmovl %%eax,%%gs\n"  
  56.                 "\tmovl %%eax,%%ss" : : : "eax");   
  57.        
  58.     __asm__ __volatile__ ("ljmp $0x0008,%0"  
  59.                 :   
  60.                 : "i" ((void *)(0x1000 - sizeof (real_mode_switch) - 100)));   
  61. }  

  首先是准备了与实模式对应的GDT表和IDT表。其中GDT表是为段寄存器设置的。段寄存器的可见部分是16为,还有48位不可见部分为对应的GDT表项,制定了段基址和段长度。machine_real_restart只是用了第三个,即基址为0x100,长度为64K的段,该entry偏移为0x10。

  • 1
  • 2
  • 3
  • 下一页

相关内容