编写操作系统之使用GRUB multiboot

【原文】www.oldlinux.org/oldlinux/viewthread.php

Multiboot Specification
www.gnu.org/software/grub/manual/multiboot/multiboot.html#multiboot_002eh

编写操作系统之使用GRUB

[这个贴子最后由sololxy在 2006/04/24 03:21am 第 2 次编辑]

9_454.rar (18.26 KB)
使用GRUB
                                    翻译 :   solo_lxy
                                    E-mail :  
                                    QQ     :   36727308
                                    MSN :  
(GRUB的英文主页可以在http://www.gnu.org/software/grub/grub.en.html 上找到)
       本教程是由Chris Giese贡献的,可以到其网站(这个地址已经无效了 )参看英文版。
=========================================================
                     参考
=========================================================
这些都是我在使用GRUB时经验积累,可以在alt.os.development(我也没上去,郁闷拉)上找到相关帖子的原版:
Subject: Re: generic bootloader question
Newsgroups: alt.os.development
From: "Marv"
Date: Sat, 7 Apr 2001 23:35:20 +0100
References: <9antu8$glc$>
Message-ID: <986682856.680474@dionysos>
Subject: Re: Grub multiboot example
Newsgroups: alt.os.development
From: "Marv"
Date: Mon, 4 Jun 2001 17:21:17 +0100
References: <>
Message-ID:
Subject: Re: Grub multiboot example
Newsgroups: alt.os.development
From: "Mike Wimpy"
Date: Thu, 7 Jun 2001 22:17:51 -0700
References: <> <> <> <> <> <>
Message-ID: <>
Subject: Re: grub coff (solved it!)
Newsgroups: alt.os.development
From: "Mark & Candice White"
Date: Sun, 16 Sep 2001 10:57:34 GMT
References:
Message-ID:

获取GRUB
原代码地址:
可执行程序:
DOS和Windows用户所需要的PARTCOPY和RAWRITE工具可以在以下网址下载:
      http://www.execpc.com/~geezer/johnfine/index.htm#zero
      http://uranus.it.swin.edu.au/~jn/linux/rawwrite.htm
      http://www.tux.org/pub/dos/rawrite/
建立GRUB
类Unix环境可以在shell上执行以下命令:
> ./configure
> make
(…我想应该是这样的…)
(实在不行可以参看源代码中的Readme、FAQ或INSTALL文件)
DOS和Windows用户请参看源代码中的安装文档。(作者并没有给出相关方法,我也不想在这上面花时间,因为我不怎么喜欢在DO或Windows上做这样的事情)
在没有文件系统的软盘上安装GRUB
1.        获取GRUB的二进制文件(stage1和stage2)
2.        合并stage1、stage2到一个文件:
(DOS)       > copy   /b stage1 + stage2 boot
(UNIX)        > cat stage1 stage2 > boot
3.        直接将boot文件写进软盘:
(DOS)       > rewrite boot a:
或者          > partcopy boot 0 168000 –f0
(UNIX)        > cat stage1 stage2 > boot
使用partcopy时它可能会报告说boot文件大小不到0x168000字节,不过不要紧。
在已经安装了文件系统的软盘上安装GRUB
1.        按照上面的介绍制作一个带有GRUB可以启动的软盘。
2.        将stage1和stage2拷贝到另外一张安装了文件系统的软盘上,注意该软盘的文件系统必须是GRUB可以识别的。为了使用GRUB的 setup命令,stage1和stage2必须放在安装了文件系统的软盘的/boot/grub目录下,具体操作如下:
(DOS)   > mkdir a:boot
             > mkdir a:bootgrub
             > copy stage1 a:bootgrub
             > copy stage2 a:bootgrub
(UNIX) > mount /dev/fd0 /mnt
      > mkdir /mnt/boot
      > mkdir /mnt/boot/grub
> cp stage1 /mnt/boot/grub
               > cp stage2 /mnt/boot/grub
3. 在第二个软盘上安装了GRUB后,就不要再修改、移动、删除或经过碎片整理了。一旦被修改,这个软盘就不能在用于启动了!为了防止修改,可以让这个文件变成只读的:
       (DOS) attrib +r +s stage2
(UNIX)   chmod a-w stage2
以上的DOS命令使得stage2变成一个只读的系统文件。这样可以防止碎片整理。
注意:stage1文件此时已经被拷贝至启动扇区。如果这个文件在GRUB安装后被删除,则该软盘还是可以启动的,这与stage2不同。
3.        用不带文件系统的软盘启动电脑。当GRUB提示符出现后,取出软盘,插入拷贝了stage1和stage2的带有文件系统的第二张软盘。
4.        如果stage1和stage2已经在/boot/grub目录中,那么可以简单的键入如下命令安装GRUB:
   setup (fd0)
这条命令显然同下面的命令等效:
   install /boot/grub/stage1 d (fd0) /boot/grub/stage2 p
            /boot/grub/menu.lst
如果stage1和stage2在其他位置,如在子文件夹/foo中,那么按照如下的方式安装:
   install = (fd0)/foo/stage1 (fd0) (fd0)/foo/stage2 0x800 p
                (fd0)/foo/menu.lst
这样带有文件系统的这张软盘就可以作为启动了盘了。
xxx - 从第二张软盘上启动,拷贝一个新的stage2并修改其访问属性,然后重新执行setup或install,这样可行吗?(xxx – GRUB并不是一个shell – 它不能拷贝文件或是列出文件夹 – 不是吗?)
xxx – 安装语法:
          0x8000,这个值被写入软盘的驱动扇区,它指示stage2被加载到内存后的首地址。
   P,修改stage2,告诉内核stage2所在的分区(我想是这样的)。
   (fd0)/boot/grub/menu.lst,修改stage2,告诉GRUB到哪儿去加载menu.lst(启动
菜单)配置文件。
         
制作多启动内核
       无论你的内核是什么样的文件格式,你的内核必须要有多启动头(multiboot header)。这个头必须是4字节对齐,并且必须在内核的前8k字节以前。
注意:一个.text段前8K的地址不必是某个开始文件的前8K.
ELF内核
       GRUB可以直接识别ELF文件格式。如果你的内核是ELF内核,可以使用下面简单的多启动头:
MULTIBOOT_PAGE_ALIGN           equ 1<<0
MULTIBOOT_MEMORY_INFO       equ 1<<1
MULTIBOOT_HEADER_MAGIC       equ 0x1BADB002
MULTIBOOT_HEADER_FLAGS       equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO
CHECKSUM                       equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
; 多启动头( NASM 语法)
ALIGN 4
DD MULTIBOOT_HEADER_MAGIC
DD MULTIBOOT_HEADER_FLAGS
DD CHECKSUM
将上面的代码放在靠近内核启动代码的开始处,然后建立内核。在建立的内核以后,使用GRUB的mbchk功能函数测试内核是否能用于多启动
内核的加载地址
       GRUB从ELF内核文件中读取物理地址(加载地址;LMA)。这个值必须大于或等于1M,并且小于真实的物理RAM的大小。
       如果加载地址在1M以下,会发生#7号错误:
            Loading below 1MB is not suppored
注意:这是GRUB的限制,并不是多启动机制的限制。
如果你使用了像0xC0000000这样的地址,显然是溢出了,同样会得到#7号错误。
注意:mbchk并不会检查这样的错误。
       一般情况下,物理地址就是虚拟地址VMA,这可以在连接脚本或连接器的命令行中设置(“ld –Ttext=0x100000 …”)。如果你的连接器版本支持的话,物理地址和虚拟地址可以用“AT”在连接脚本里独立的指定:
       OUTPUT_FORMAT("elf32-i386"
       ENTRY(entry)
       virt = 0xC0000000; / 3 G /
       phys = 0x100000;   / 1 M /
       SECTIONS
       {
         .text virt : AT(phys)
         { code = .;
            (.text)
            . = ALIGN(4096);
         }
         .data :   AT(phys + (data - code))
         { data = .;
           
(.data)
            . = ALIGN(4096);
         }
         .bss :   AT(phys + (bss - code))
         { bss = .;
            (.bss)
           
(COMMON)
            . = ALIGN(4096); }
            end = .;
         }
       }
连接完成后,使用objdump –h查看地址是否正确。
DJGPP的COFF文件格式和其他文件格式
       DJGPP用户可以使用下面这个工具制作ELF文件。
            http://www.multimania.com/placr/binutils.html
(xxx – 这个服务器一般很难打开的,如果你愿意可以给它作个镜像。我无能为力,因为我的磁盘空间不够)
       我推荐建立标准的COFF文件,然后这样做:
            Objcopy-elf –O elf32-i386 krnl.cof kenl.elf
       如果这样不行的话,你可以使用“AOUT KLUDGE”让GRUB加载COFF文件。这需要在多启动头的后面添加额外的域(field),像下面这样:
MULTIBOOT_PAGE_ALIGN           equ 1<<0
MULTIBOOT_MEMORY_INFO       equ 1<<1
MULTIBOOT_AOUT_KLUDGE       equ 1<<16
MULTIBOOT_HEADER_MAGIC       equ 0x1BADB002
MULTIBOOT_HEADER_FLAGS          equ MULTIBOOT_PAGE_ALIGN| MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE
CHECKSUM                      equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
; The Multiboot header
ALIGN 4
mboot:
       DD MULTIBOOT_HEADER_MAGIC
       DD MULTIBOOT_HEADER_FLAGS
       DD CHECKSUM
       ;如果设置了在MULTIBOOT_HEADER_FLAGS
       ;中设置了MULTIBOOT_AOUT_KLUDGE
       DD mboot       ; these are PHYSICAL addresses
       DD code       ; start of kernel .text (code) section
       DD edata       ; end of kernel .data section
       DD end           ; end of kernel BSS
       DD start                 ; kernel entry point (initial EIP)
注意:如果是二进制或其他文件格式,则同样需要“AOUT KLUDGE”。(xxx – 没有测试过,也许是正确的)。
启动内核
1.        确定你的内核在GRUB方便找到的位置。不必非得在软盘上(靠,这个真COOL)。
2.        从GRUB软盘上启动。
3.        (可选的)告诉GRUB,它的根文件夹所使用的设备。
         root (hd0,1)
这将挂载(mount)第一块硬盘的第二个主分区,以其作为做为根文件夹。
4.        告诉GRUB你的内核在哪儿:
         kernel   /krnl.elf
如果你没有指定根设备,就必须在路径名上显式指定:
         kernel   (hd0,1)/krnl.elf
5.        如果GRUB没有出现任何错误,那么就可以直接启动了:
         boot
利用GRUB向你的内核传递信息
启动后,在32位内核进入点,机器状态如下:
1.        CS指向基地址为0x00000000,限长为4G – 1的代码段描述符。
2.        DS,SS,ES,FS和GS指向基地址为0x00000000,限长为4G – 1的数据段描述符。
3.        A20地址线已经打开。
4.        页机制被禁止。
5.        中断被禁止。
6.        EAX = 0x2BADB002
7.        系统信息和启动信息块的线性地址保存在 EBX中(相当于一个指针)。
            / 多启动信息结构 /
       typedef struct multiboot_info
       {
      unsigned long flags;
      unsigned long mem_lower;
      unsigned long mem_upper;
      unsigned long boot_device;
      unsigned long cmdline;
      unsigned long mods_count;
      unsigned long mods_addr;
      union
      {
         aout_symbol_table_t aout_sym;
         elf_section_header_table_t elf_sec;
          } u;
      unsigned long mmap_length;
      unsigned long mmap_addr;
      } multiboot_info_t;
       在调用main函数之前将EBX的值压入堆栈,就可以使用C代码来访问以上结构中的相关信息了,示例代码如下:
汇编启动代码:
            …
            push ebx
            call main ; “call main” for Linux/ELF
            …
C语言代码:
            #include <…>
            int main(multiboot_info_t boot_info)
            {
                     if( boot_info->flags & 2)
                     {
                               Kprintf(“the command line is:’%s’n”,
                     (char
) boot_info->cmdline);
                     }
                     …
            }
制作启动菜单
示例1:
       # Entry 0:
       title WildMagnolia
       root (fd0)
       kernel   /boot/kernel.elf
       module   /boot/mod_a
       module   /boot/mod_b
示例2:
       #
       # Sample boot menu configuration file
       #
       #   default - boot the first entry.
       default 0
       # if have problem - boot the second entry.
       fallback 1
       # after 30 sec boot default.
       timeout 30
       # GNU Hurd
       title       GNU/Hurd
       root           (hd0,0)
       kernel       /boot/gnumach.gz root=hd0s1
       module       /boot/serverboot.gz
       # Linux - boot ot second HDD
       title       GNU/Linux
       kernel        (hd1,0)/vmlinuz root=/dev/hdb1
       # booting Mach - get kernel from floppy
       title       Utah Mach4 multiboot
       root           (hd0,2)
       pause       Insert the diskette now!!
       kernel       (fd0)/boot/kernel root=hd0s3
       module        (fd0)/boot/bootstrap
       # booting OS/2
       title       OS/2
       root       (hd0,1)
       makeactive
       # chainload OS/2 bootloader from the first sector
       chainloader +1
       # For booting Windows NT or Windows95
       title       Windows NT / Windows 95 boot menu
       root     (hd0,0)
       makeactive
       chainloader +1
       # za boot na DOS ako Windows NT e instaliran
       # chainload /bootsect.dos
       # Colors change :0).
       title                 Change the colors
       color                 light-green/brown blink-red/blue

为内核加载模块(module)
=======================================================================
xxx
=======================================================================
=======================================================================
Other
=======================================================================
(看来作者并没有写全这些,真遗憾!
- gzip-compressed kernels?用gzip压缩的内核如何处理呢?
- does GRUB understand kernel file formats other than ELF?
- GRUB除了可以处理ELF内核以外还可以处理其他的吗?
by K.J.
Yes, you may also compile your kernel as a COFF file or an AOUT file.
K.J回答说你可以把你的内核编译成COFF或AOUT文件。(纯二进制文件一样是可以处理的,我正在写的一个内核就是使用纯二进制的,它可以让我更清楚的知道我的代码在内存的什么位置,这样对我来说更了然入胸。)

(在使用GRUB的过程中也出现了不少问题,有些到现在还没搞得太清楚,那位大虾在行的话不妨让我请教请教,谢谢了! 转贴请注明原文和翻译的文章,也不枉我敲了两个小时的中文,谢谢 )