歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux教程

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。

Copyright © Linux教程網 All Rights Reserved