Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1092912
  • 博文数量: 104
  • 博客积分: 3715
  • 博客等级: 中校
  • 技术积分: 1868
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-30 08:38
文章分类

全部博文(104)

文章存档

2013年(1)

2012年(9)

2011年(41)

2010年(3)

2009年(3)

2008年(47)

分类: 系统运维

2011-01-10 11:04:45

    本文分析Minix3的BootLoader。
* masterboot.s
    该汇编文件中包含了主引导代码。这个代码一般被放在软盘、硬盘的第一个扇区。对于硬盘而言,这段代码
就是其主引导记录(MBR)。总体来讲,这段代码的主要工作如下:
    1.  如果启动设备是一个硬盘,并且它有一个分区是活动分区,启动那个活动分区;
    2.  否则,启动下一个软盘或者硬盘;
    举例:一个可能的启动顺序是:
    /dev/fd0    -- 尝试软盘0;
    /dev/fd1    -- 尝试软盘1;
    /dev/c0d0   -- 尝试硬盘0,其MBR找到并选择启动活动分区(主分区)2;
    /dev/c0d0p2 -- 扩展分区的主启动记录,找到并选择活动扩展分区0;
    /dev/c0d0p2s0 -- Minix启动快,读取并运行启动管理器/boot/boot;

    想要理解这个文件中的代码,需要对磁盘的MBR有一定的了解(典型的MBR,包括本文件中的代码都如此)。
    MBR是主引导记录(Master Boot Record)的缩写,它是一个“可分区存储设备”,例如硬盘,的第一个扇区。
MBR的大小是512Bytes。MBR的主要内容/工作包括:
    1.  记录磁盘的分区表;
    2.  负责启动OS;
    3.  提供磁盘签名(用于标记磁盘类型);
    MBR有一个签名,“AA55”,在little-endian上是"55 AA"。在IA32系统上的一个典型的用例是:
    1.  BIOS完成加电自检后,调用int 19。
    2.  int 19尝试读取软盘的启动扇区,并将其加载到内存0000:7c00处,然后跳转到0000:7c00执行;
    3.  如果软盘上没有找到启动扇区,int 19会尝试将第一个硬盘的MBR读取到内存的0000:7c00,并跳转到
        该地址执行;
    4.  MBR会找到第一个活动分区,并将那个分区的引导扇区读取到0000:7c00,跳转0000:7c00开始执行;
    5.  活动分区的引导扇区负责继续启动操作系统,每个操作系统的引导扇区都可能不同;
    详细的磁盘、引导信息,网上有很多说明,此处不再赘述。

    从updateboot.sh脚本可以看出,/boot/boot是启动监视器,而bootblock则是minix的boot sector的代码。

* bootblock.s
    这是minix的引导扇区,在MBR被执行后,最终会找到这个扇区,并把它加载到0000:0x7c00处执行。该文件
中的代码负责启动boot程序(boot被加载到地址为0x1000的内存位置),即Minix3的启动监视器。/boot/boot
本身在磁盘上的地址会被installboot工具patch到bootblock中,包括24位的起始扇区号和8位的长度(扇区数)。
    
    由此看出,真正启动minix的是boot程序。从编译脚本看出,boot程序直接依赖的几个程序是boothead.s,
boot.[ch],bootimage.c,rawfs.[ch]。
    bootblock执行的最后一步是启动boot程序,它执行完跳转后,实际上跳转到了boothead.s中的代码开始执行。
boothead.s最后会启动boot.c中的代码,入口是boot()函数。

* boothead.s
    该文件包括两类内容:
    1.  启动代码,这部分首先设置一些C变量,之后会调用boot.c中的boot()函数;
    2.  底层处理函数定义,这部分定义了一些公用的函数,可供boot.c调用,主要处理一些低级的事务,例如
        磁盘/tty/键盘IO,内存拷贝等。这些函数都是用BIOS实现。
    启动代码的主要工作包括:
    1.  根据编译器给的a.out的header设置寄存器状态(代码和数据仍然在0x1000附近);
    2.  设置启动参数到C定义的变量,如_device、_rem_part、_cdbooted等;
    3.  记录当前的vedio模式;
    4.  保存代码段、数据段的绝对地址到C定义的变量_caddr、_daddr中;
    5.  将boot程序运行所占用的总大小放入C定义的变量_runsize中;
    6.  探测内存entries,及地址空间信息;
    7.  调用_boot()函数,进入C代码。

* rawfs.[hc]
    由于在BIOS模式下没有文件系统,这个模块用于提供简单的对(Raw Minix Filesyste)文件系统的访问操作。
使得boot.c可以读取文件系统中的启动映像文件和其它必须的文件。该模块唯一依赖的一个外部函数是
readblock(),用于读取文件系统的一个块。该函数由boot.c提供(后者由依赖与boothead.s)。
    该模块几个主要的函数是:
    1.  r_super():读取超级块并初始化一些内部数据结构;
    2.  r_stat():获取指定的ino对应的文件的状态信息;
    3.  r_vir2abs():将一个文件内的块号转换为磁盘的物理块号;
    4.  r_lookup():将一个路径名转换成一个ino号;
    5.  r_readdir():读取一个指定目录的下一个目录项;

* boot.[hc]
    该文件实现了minix3的boot monitor。它有两个入口:如果是在系统启动后执行的(及POSIX环境下),它的
入口是main()函数。而如果是一个系统正在启动时执行的,此时没有POSIX环境,则在BIOS环境下执行,入口是
boot()函数。(这两个入口不是同时可用的,在BIOS环境下,其运行在16位非保护模式下。每次编译只能生成
一个二进制文件,要么在BIOS下执行,要么在POSIX下执行)。
    在POSIX下执行时,boot调用的外部函数,例如exit()等,有当前系统的标准库提供;而在BIOS下执行时,
则是由相应的boothead.s提供的。
    该文件实现的是monitor,可以接收用户命令并作相应的事情。而加载和启动minix的代码在bootimage.c中,
后者有一个函数bootminix(),是入口。
    下面,我们重点分析BIOS环境下的boot.c文件,当涉及到常量或其它定义时,可能谈到boot.h文件。文件头
不将EXTERN定义为空,可见,在boot.h中声明的那些全局变量都是实现在该文件中的。
    boot()函数的工作分为三步:
    1.  调用initialize()初始化一些全局的数据结构;
    2.  调用get_parameters()从参数扇区获取启动参数;
    3.  进入一个循环:
        a.  如果当前命令不为空,调用execute()执行命令;
        b.  调用monitor()等待并读取/分析下一个/组用户输入的命令;
    由上可见,boot很重要的一个功能就是读取、分析并执行命令和命令序列。在进一步分析各个函数的主要
功能之前,我们先看看boot支持命令的方式。
** boot monitor command
    所有支持的命令有resnames定义,包括一组枚举值和对应的命令的字符串表示。Command的基本元素是token,
由token结构体表示。

    我们来大体看一下这几个函数。
** initialize()
    1.  初始化mem[]表项:
        它是由boothead.s探索出来的内存区段中类型为1的前三个构成的,应该就是程序可以使用的内存;
    2.  将boot的代码、数据移动到低端内存(mem[0])的最后部分,如果移动的结果会跨越640K那个地址,
        则移动到640K之前的最后部分,这是为minix内核的加载腾出空间;
    3.  调用relocate()函数后,boot.c继续执行,但执行的是移动后的代码了;
    4.  如果mem[1]的大小超过512K,那么,会保持boot monitor一直驻留内存,通过把它占用的地址空间
        从mem[0]中去掉而做到,由此可见,mem数组是后续内核使用的内存映射表;
    5.  去掉mem[0]前2K的空间;
    6.  初始化bootdev结构体;
** get_parameters()
    1.  设置全局的环境变量:
        a.  rootdev
        b.  ramimagedef
        c.  ramsize
        d.  processer
        e.  bus
        f.  video
        g.  chrome
        h.  memory
        i.  image
        j.  等等;
    2.  读取参数扇区,并分析,将参数扇区中的commands放入全局的cmds变量中,这些命令
        会被boot直接执行,默认情况下,它们就是等待一段时间后启动minix。如果等待中有用户输入,
        则取消minix的启动,进而执行用户的命令。
** execute()
    执行当前cmds中的命令链。其中,最重要的一个命令是boot,它执行bootminix()方法,用于启动minix。
** monitor()
    打印提示符,读取一行命令并分析。

    至此,真正启动minix之前的流程都走完了。而bootminix()方法则会真正的加载minix的image并最终将
控制权交给真正的minix系统。
    待续......

阅读(2814) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~