3. linux/arch/i386/boot/bootsect.S

假设我们要启动 bzImage,它由 bbootsectbsetupbvmlinux (head.o, misc.o, piggy.o) 组成,第一个软盘扇区,bbootsect (512 字节),它由以下代码编译而来linux/arch/i386/boot/bootsect.S, 由 BIOS 加载到 07C0:0。bzImage 的其余部分(bsetupbvmlinux)尚未加载。

3.1. 移动引导扇区

SETUPSECTS      = 4                     /* default nr of setup-sectors */
BOOTSEG         = 0x07C0                /* original address of boot-sector */
INITSEG         = DEF_INITSEG  (0x9000) /* we move boot here - out of the way */
SETUPSEG        = DEF_SETUPSEG (0x9020) /* setup starts here */
SYSSEG          = DEF_SYSSEG   (0x1000) /* system loaded at 0x10000 (65536) */
SYSSIZE         = DEF_SYSSIZE  (0x7F00) /* system size: # of 16-byte clicks */
                                        /* to be loaded */
ROOT_DEV        = 0                     /* ROOT_DEV is now written by "build" */
SWAP_DEV        = 0                     /* SWAP_DEV is now written by "build" */

.code16
.text

///////////////////////////////////////////////////////////////////////////////
_start:
{
        // move ourself from 0x7C00 to 0x90000 and jump there.
        move BOOTSEG:0 to INITSEG:0 (512 bytes);
        goto INITSEG:go;
}
bbootsect 已被移动到 INITSEG:0 (0x9000:0)。现在我们可以忘记 BOOTSEG。

3.2. 获取磁盘参数

///////////////////////////////////////////////////////////////////////////////
// prepare stack and disk parameter table
go:
{
        SS:SP = INITSEG:3FF4;   // put stack at INITSEG:0x4000-12
        /* 0x4000 is an arbitrary value >=
         *   length of bootsect + length of setup + room for stack;
         * 12 is disk parm size. */
        copy disk parameter (pointer in 0:0078) to INITSEG:3FF4 (12 bytes);
        // int1E: SYSTEM DATA - DISKETTE PARAMETERS
        patch sector count to 36 (offset 4 in parameter table, 1 byte);
        set disk parameter table pointer (0:0078, int1E) to INITSEG:3FF4;
}
确保在 SS 寄存器之后立即初始化 SP。根据 IA-32 Intel 架构软件开发者手册(Vol.3. Ch.5.8.3. Masking Exceptions and Interrupts When Switching Stacks),推荐使用 "lss" 指令来修改 SS。

堆栈操作,例如 push 和 pop,现在可以正常工作了。磁盘参数的前 12 个字节已被复制到 INITSEG:3FF4。

///////////////////////////////////////////////////////////////////////////////
// get disk drive parameters, specifically number of sectors/track.
        char disksizes[] = {36, 18, 15, 9};
        int sectors;
{
        SI = disksizes;                         // i = 0;
        do {
probe_loop:
                sectors = DS:[SI++];            // sectors = disksizes[i++];
                if (SI>=disksizes+4) break;     // if (i>=4) break;
                int13/AH=02h(AL=1, ES:BX=INITSEG:0200, CX=sectors, DX=0);
                // int13/AH=02h: DISK - READ SECTOR(S) INTO MEMORY
        } while (failed to read sectors);
}
"lodsb" 从 DS:[SI] 加载一个字节到 AL,并自动增加 SI。

每磁道扇区数已保存在变量 sectors 中。

3.3. 加载 Setup 代码

bsetup (setup_sects 扇区) 将紧随 bbootsect 之后加载,即 SETUPSEG:0。请注意,INITSEG:0200==SETUPSEG:0,并且 setup_sects 已被 tools/build第 2.6 节 中更改,以匹配 bsetup 的大小。

///////////////////////////////////////////////////////////////////////////////
got_sectors:
        word sread;             // sectors read for current track
        char setup_sects;       // overwritten by tools/build
{
        print out "Loading";
        /* int10/AH=03h(BH=0): VIDEO - GET CURSOR POSITION AND SIZE
         * int10/AH=13h(AL=1, BH=0, BL=7, CX=9, DH=DL=0, ES:BP=INITSEG:$msg1):
         *   VIDEO - WRITE STRING */

        // load setup-sectors directly after the moved bootblock (at 0x90200).
        SI = &sread;            // using SI to index sread, head and track
        sread = 1;              // the boot sector has already been read

        int13/AH=00h(DL=0);     // reset FDC

        BX = 0x0200;            // read bsetup right after bbootsect (512 bytes)
        do {
next_step:
                /* to prevent cylinder crossing reading,
                 *   calculate how many sectors to read this time */
                uint16 pushw_ax = AX = MIN(sectors-sread, setup_sects);
no_cyl_crossing:
                read_track(AL, ES:BX);          // AX is not modified
                // set ES:BX, sread, head and track for next read_track()
                set_next(AX);
                setup_sects -= pushw_ax;        // rest - for next step
        } while (setup_sects);
}
SI 被设置为 sread 的地址,以索引变量 sreadheadtrack,因为它们在内存中是连续的。查看 第 3.6 节 以了解 read_track() 和 set_next() 的详细信息。

3.4. 加载压缩镜像

bvmlinux (head.o, misc.o, piggy.o) 将加载到 0x100000,大小为 syssize*16 字节。

///////////////////////////////////////////////////////////////////////////////
// load vmlinux/bvmlinux (head.o, misc.o, piggy.o)
{
        read_it(ES=SYSSEG);
        kill_motor();                           // turn off floppy drive motor
        print_nl();                             // print CR LF
}
查看 第 3.6 节 以了解 read_it() 的详细信息。如果我们启动的是 zImagevmlinux 将加载到 0x10000 (SYSSEG:0)。

bzImage (bbootsect, bsetup, bvmlinux) 现在作为一个整体存在于内存中。

3.5. 进入 Setup

///////////////////////////////////////////////////////////////////////////////
// check which root-device to use and jump to setup.S
        int root_dev;                           // overwritten by tools/build
{
        if (!root_dev) {
                switch (sectors) {
                case 15: root_dev = 0x0208;     // /dev/ps0 - 1.2Mb
                        break;
                case 18: root_dev = 0x021C;     // /dev/PS0 - 1.44Mb
                        break;
                case 36: root_dev = 0x0220;     // /dev/fd0H2880 - 2.88Mb
                        break;
                default: root_dev = 0x0200;     // /dev/fd0 - auto detect
                        break;
                }
        }

        // jump to the setup-routine loaded directly after the bootblock
        goto SETUPSEG:0;
}
它将控制权传递给 bsetup。请参阅 第 4 节 中的 linux/arch/i386/boot/setup.S:start

3.6. 读取磁盘

以下函数用于从磁盘加载 bsetupbvmlinux。请注意,syssize 也已被 tools/build第 2.6 节 中更改。
sread:  .word 0                         # sectors read of current track
head:   .word 0                         # current head
track:  .word 0                         # current track
///////////////////////////////////////////////////////////////////////////////
// load the system image at address SYSSEG:0
read_it(ES=SYSSEG)
        int syssize;                    /* system size in 16-bytes,
                                         *   overwritten by tools/build */
{
        if (ES & 0x0fff) die;           // not 64KB aligned

        BX = 0;
        for (;;) {
rp_read:
#ifdef __BIG_KERNEL__
                bootsect_helper(ES:BX);
                /* INITSEG:0220==SETUPSEG:0020 is bootsect_kludge,
                 *   which contains pointer SETUPSEG:bootsect_helper().
                 * This function initializes some data structures
                 *   when it is called for the first time,
                 *   and moves SYSSEG:0 to 0x100000, 64KB each time,
                 *   in the following calls.
                 * See Section 3.7. */
#else
                AX = ES - SYSSEG + ( BX >> 4);  // how many 16-bytes read
#endif
                if (AX > syssize) return;       // everything loaded
ok1_read:
                /* Get proper AL (sectors to read) for this time
                 *   to prevent cylinder crossing reading and BX overflow. */
                AX = sectors - sread;
                CX = BX + (AX << 9);            // 1 sector = 2^9 bytes
                if (CX overflow && CX!=0) {     // > 64KB
                        AX = (-BX) >> 9;
                }
ok2_read:
                read_track(AL, ES:BX);
                set_next(AX);
        }
}

///////////////////////////////////////////////////////////////////////////////
// read disk with parameters (sread, track, head)
read_track(AL sectors, ES:BX destination)
{
        for (;;) {
                printf(".");
                // int10/AH=0Eh: VIDEO - TELETYPE OUTPUT

                // set CX, DX according to (sread, track, head)
                DX = track;
                CX = sread + 1;
                CH = DL;

                DX = head;
                DH = DL;
                DX &= 0x0100;

                int13/AH=02h(AL, ES:BX, CX, DX);
                // int13/AH=02h: DISK - READ SECTOR(S) INTO MEMORY
                if (read disk success) return;
                // "addw $8, %sp" is to cancel previous 4 "pushw" operations.
bad_rt:
                print_all();            // print error code, AX, BX, CX and DX
                int13/AH=00h(DL=0);     // reset FDC
        }
}

///////////////////////////////////////////////////////////////////////////////
// set ES:BX, sread, head and track for next read_track()
set_next(AX sectors_read)
{
        CX = AX;                        // sectors read
        AX += sread;
        if (AX==sectors) {
                head = 1 ^ head;        // flap head between 0 and 1
                if (head==0) track++;
ok4_set:
                AX = 0;
        }
ok3_set:
        sread = AX;
        BX += CX && 9;
        if (BX overflow) {              // > 64KB
                ES += 0x1000;
                BX = 0;
        }
set_next_fn:
}

3.7. Bootsect 助手函数

setup.S:bootsect_helper() 仅被 bootsect.S:read_it() 使用。

由于 bbootsectbsetup 是分别链接的,它们使用相对于自身代码/数据段的偏移量。我们必须对不同段中的 bootsect_helper() 进行“远调用”(lcall),然后它必须“远返回”(lret)。这导致调用中的 CS 改变,这使得 CS!=DS,我们必须使用段修饰符来指定setup.S 中的变量.

///////////////////////////////////////////////////////////////////////////////
// called by bootsect loader when loading bzImage
bootsect_helper(ES:BX)
        bootsect_es = 0;                // defined in setup.S
        type_of_loader = 0;             // defined in setup.S
{
        if (!bootsect_es) {             // called for the first time
                type_of_loader = 0x20;  // bootsect-loader, version 0
                AX = ES >> 4;
                *(byte*)(&bootsect_src_base+2) = AH;
                bootsect_es = ES;
                AX = ES - SYSSEG;
                return;
        }
bootsect_second:
        if (!BX) {                      // 64KB full
                // move from SYSSEG:0 to destination, 64KB each time
                int15/AH=87h(CX=0x8000, ES:SI=CS:bootsect_gdt);
                // int15/AH=87h: SYSTEM - COPY EXTENDED MEMORY
                if (failed to copy) {
                        bootsect_panic() {
                                prtstr("INT15 refuses to access high mem, "
                                        "giving up.");
bootsect_panic_loop:            goto bootsect_panic_loop;   // never return
                        }
                }
                ES = bootsect_es;       // reset ES to always point to 0x10000
                *(byte*)(&bootsect_dst_base+2)++;
        }
bootsect_ex:
        // have the number of moved frames (16-bytes) in AX
        AH = *(byte*)(&bootsect_dst_base+2) << 4;
        AL = 0;
}

///////////////////////////////////////////////////////////////////////////////
// data used by bootsect_helper()
bootsect_gdt:
        .word   0, 0, 0, 0
        .word   0, 0, 0, 0

bootsect_src:
        .word   0xffff

bootsect_src_base:
        .byte   0x00, 0x00, 0x01                # base = 0x010000
        .byte   0x93                            # typbyte
        .word   0                               # limit16,base24 =0

bootsect_dst:
        .word   0xffff

bootsect_dst_base:
        .byte   0x00, 0x00, 0x10                # base = 0x100000
        .byte   0x93                            # typbyte
        .word   0                               # limit16,base24 =0
        .word   0, 0, 0, 0                      # BIOS CS
        .word   0, 0, 0, 0                      # BIOS DS

bootsect_es:
        .word   0

bootsect_panic_mess:
        .string "INT15 refuses to access high mem, giving up."
请注意,type_of_loader 值已更改。它将在 第 4.3 节 中被引用。

3.8. 其他

其余部分是支持函数、变量和“实模式内核头”的一部分。请注意,数据在 .text 段中作为代码存在,因此可以在加载时正确初始化。
///////////////////////////////////////////////////////////////////////////////
// some small functions
print_all();  /* print error code, AX, BX, CX and DX */
print_nl();   /* print CR LF */
print_hex();  /* print the word pointed to by SS:BP in hexadecimal */
kill_motor()  /* turn off floppy drive motor */
{
#if 1
        int13/AH=00h(DL=0);     // reset FDC
#else
        outb(0, 0x3F2);         // outb(val, port)
#endif
}

///////////////////////////////////////////////////////////////////////////////
sectors:        .word 0
disksizes:      .byte 36, 18, 15, 9
msg1:           .byte 13, 10
                .ascii "Loading"

引导扇区尾部,它是“实模式内核头”的一部分,从偏移量 497 开始。
.org 497
setup_sects:    .byte SETUPSECS         // overwritten by tools/build
root_flags:     .word ROOT_RDONLY
syssize:        .word SYSSIZE           // overwritten by tools/build
swap_dev:       .word SWAP_DEV
ram_size:       .word RAMDISK
vid_mode:       .word SVGA_MODE
root_dev:       .word ROOT_DEV          // overwritten by tools/build
boot_flag:      .word 0xAA55

这个“头”必须符合以下布局模式linux/Documentation/i386/boot.txt:
Offset  Proto   Name            Meaning
/Size
01F1/1  ALL     setup_sects     The size of the setup in sectors
01F2/2  ALL     root_flags      If set, the root is mounted readonly
01F4/2  ALL     syssize         DO NOT USE - for bootsect.S use only
01F6/2  ALL     swap_dev        DO NOT USE - obsolete
01F8/2  ALL     ram_size        DO NOT USE - for bootsect.S use only
01FA/2  ALL     vid_mode        Video mode control
01FC/2  ALL     root_dev        Default root device number
01FE/2  ALL     boot_flag       0xAA55 magic number

3.9. 参考

由于 <IA-32 Intel 架构软件开发者手册> 在本文档中被广泛引用,我将简称其为“IA-32 手册”。