1.改写 bootsect.s

使得bootsect.s 能在屏幕上打印一段提示信息“XXX is booting...”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等(可以上论坛上秀秀谁的 OS 名字最帅,也可以显示一个特色 logo,以表示自己操作系统的与众不同。)

2.改写 setup.s

  1. bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。
  2. setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。
  3. setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。

3.回答问题

有时,继承传统意味着别手蹩脚。x86 计算机为了向下兼容,导致启动过程比较复杂。请找出 x86 计算机启动过程中,被硬件强制,软件必须遵守的两个“多此一举”的步骤(多找几个也无妨),说说它们为什么多此一举,并设计更简洁的替代方案。

实验过程

1.bootsect.s屏幕文字输出

! 文件:bootsect.s
entry _start
_start:
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#36
    mov bx,#0x0007
    mov bp,#msg1
    mov ax,#0x07c0
    mov es,ax
    mov ax,#0x1301
    int 0x10
inf_loop:
    jmp inf_loop
msg1:
    .byte   13,10
    .ascii "Hello,my name is MochiXu!"
    .byte   13,10,13,10
.org 510
boot_flag:
    .word   0xAA55

初步修改bootsect.s文件之后,需要编译此文件并将目标文件生成Image。

2.编译运行Image验证结果

编译运行步骤如下

  1. 编译并链接bootsect.s文件
as86 -0 -a -o bootsect.o bootsect.s
ld86 -0 -s -o bootsect bootsect.o
  1. 删除bootsect文件首部的32字节
dd bs=1 if=bootsect of=Image skip=32
  1. 运行
# 当前的工作路径为 ~/Desktop/oslab/linux-0.11/boot/
# 将刚刚生成的 Image 复制到 linux-0.11 目录下
$ cp Image ../
# 执行 oslab 目录中的 run 脚本
$ ../../run

在实验过程中有可能多次更改bootsect.s文件,更改之后仍需要编译运行,为了节省时间可以将上述编译运行操作写入一个bash脚本

#!/bin/sh
as86 -0 -a -o bootsect.o bootsect.s
ld86 -0 -s -o bootsect bootsect.o
dd bs=1 if=bootsect of=Image skip=32
cp Image ../
../../run

在更改完bootsect.s文件之后,直接在对应目录利用终端运行此脚本即可,结果如下

操作系统实验01-操作系统的引导

3.改写setup.s

! 文件setup.s
INITSEG = 0x9000

entry _start
_start:
! 在显示字符串之前必须先获取当前光标的位置,这样就可以把字符串显示到当前光标处了
    mov    ah,#0x03
    xor    bh,bh
    int    0x10
    
! 利用10号中断的13号功能打印字符串"Now we are in SETUP."
    mov    cx,#26
    mov    bx,#0x0007
    mov    bp,#msg1
    mov     ax,cs
    mov    es,ax
    mov    ax,#0x1301
    int    0x10

! 下面开始读取一些硬件参数

    ! 读入光标位置信息,保存到0x90000处
    mov    ax,#INITSEG
    mov    ds,ax
    mov    ah,#0x03
    xor    bh,bh
    int    0x10
    mov    [0],ds

    ! 读入内存大小位置信息,保存到0x90002处
    mov    ah,#0x88
    int    0x15
    mov    [2],ax

    ! 从0x41处拷贝16个字节(磁盘参数表)到0x90004处
    mov    ax,#0x0000
    mov    ds,ax
    lds    si,[4*0x41]
    mov    ax,#INITSEG
    mov    es,ax
    mov    di,#0x0004
    mov    cx,#0x10
    rep       ! 重复16次
    movsb


! 先打印光标位置
    ! 打印字符串之前要先读取光标位置,将字符串打印到当前光标处
    mov    ah,#0x03
    xor    bh,bh
    int    0x10

    ! 打印字符串 "Cursor POS:"
    mov    cx,#11
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg2
    mov    ax,#0x1301
    int    0x10    
    
    ! 调用打印函数,打印光标位置
    mov    ax,#0x9000
    mov    ds,ax
    mov    dx,0x0
    call    print_hex
    call    print_nl

! 打印内存大小
    ! 打印字符串"Memory SIZE:"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 读取光标位置

    mov    cx,#12
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg3
    mov    ax,#0x1301
    int    0x10    


    ! 调用打印函数,打印内存大小信息
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x2
    call     print_hex


    ! 打印字符串"KB"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 读取光标位置    
    
    mov    cx,#2
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg4
    mov    ax,#0x1301
    int    0x10    
    call    print_nl    

!打印柱面数
    ! 打印字符串"Cyls"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 读取光标位置

    mov    cx,#5
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg5
    mov    ax,#0x1301
    int    0x10    
    

    ! 调用打印函数打印磁盘柱面数
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x4
    call    print_hex
    call    print_nl

! 打印磁头数
    ! 打印字符串"Heads:"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 读取光标位置

    mov    cx,#6
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg6
    mov    ax,#0x1301
    int    0x10    

    
    ! 调用打印函数打印磁盘磁头数
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x6
    call    print_hex
    call    print_nl


! 打印每磁道扇区数
    ! 打印字符串"sectors"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 读取光标位置

    mov    cx,#8
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg7
    mov    ax,#0x1301
    int    0x10    

    ! 调用打印函数打印扇区数
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x12
    call    print_hex
    call    print_nl

Inf_loop:
    jmp Inf_loop    ! 无限循环


! print_hex函数:将一个数字转换为ascii码字符,并打印到屏幕上
! 参数值:dx
! 返回值:无
print_hex:
    mov    cx,#4            ! 要打印4个十六进制数字,故循环4次
print_digit:
    rol    dx,#4            ! 循环以使低4比特用上 !! 取dx的高4比特移到低4比特处
    mov    ax,#0xe0f        ! ah = 请求的功能值,al = 半字节(4个比特)掩码
    and    al,dl            ! 取dl的低4比特值
    add    al,#0x30        ! 给al数字加上十六进制0x30
    cmp    al,#0x3a    
    jl    outp            ! 如果是一个不大于十的数字
    add    al,#0x07    ! 如果是a~f,要多加7
outp:
    int    0x10
    loop    print_digit    ! 用loop重复4次
    ret

! 打印回车换行
print_nl:
    mov    ax,#0xe0d
    int    0x10
    mov    al,#0xa
    int    0x10
    ret

msg1:
    .byte 13,10
    .ascii "Now we are in SETUP."
    .byte 13,10,13,10

msg2:
    .ascii "Cursor POS:"

msg3:    
    .ascii "Memory SIZE:"

msg4:
    .ascii "KB"

msg5:    
    .ascii "Cyls:"

msg6:
    .ascii "Heads:"

msg7:
    .ascii "Sectors:"


.org 510
boot_flag:
    .word 0xAA55

在修改完setup.s文件之后需要继续修改bootsect.s文件,bootsect.s正确加载setup.s之后屏幕才会输出“Now we are in setup!”
修改后的bootsect.s文件如下

! 文件:bootsect.s

SETUPLEN = 1
SETUPSEG = 0x07e0

entry _start
_start:
! 首先利用10号中断的3号功能来读取光标位置
    mov    ah,#0x03        
    xor    bh,bh            
    int    0x10
    
! 再利用10号中断的13号功能显示字符串
    mov    cx,#50            ! 加上回车和换行,字符串一共包含50个字符,所以设置cx为50
    mov    bx,#0x0007
    mov    bp,#msg1
    mov     ax,#0x07c0
    mov     es,ax            ! es:bp=显示字符串的地址
    mov    ax,#0x1301        
    int    0x10

load_setup:
    mov    dx,#0x0000        ! 设置驱动器和磁头(drive 0, head 0): 软盘0磁头
    mov    cx,#0x0002        ! 设置扇区号和磁道(sector 2, track 0):0磁头、0磁道、2扇区
    mov    bx,#0x0200        ! 设置读入的内存地址:BOOTSEG+address = 512,偏移512字节
    mov    ax,#0x0200+SETUPLEN    ! 设置读入的扇区个数(service 2, nr of sectors),
                    ! SETUPLEN是读入的扇区个数,Linux 0.11设置的是4,
                    ! 我们不需要那么多,我们设置为1
    int    0x13            ! 应用0x13号BIOS中断读入1个setup.s扇区
    jnc    ok_load_setup        ! 读入成功,跳转到ok_load_setup: ok - continue
    mov    dx,#0x0000        ! 软驱、软盘有问题才会执行到这里
    mov    ax,#0x0000        ! 否则复位软驱
    int    0x13
    j    load_setup        ! 重新循环,再次尝试读取

ok_load_setup:
    jmpi    0,SETUPSEG

! msg1处放置要显示的字符串
msg1:
    .byte 13,10            ! 换行+回车
    .ascii "Hello,my name is MochiXu!"
    .byte 13,10,13,10        ! 两对换行+回车

! 下面是启动盘具有有效引导扇区的标志. 仅供BIOS中的程序加载扇区时识别使用。
! 它必须位于引导扇区的最后两个字节中.
.org 510
boot_flag:
    .word 0xAA55    ! 引导扇区的标记就是0XAA55

4.编译运行Image验证结果

目前有两个文件需要编译,可以重用上述脚本实现不同文件的编译,也可以直接使用Makefile进行编译链接。但是oslab内make命令的作用是生成整个内核的镜像文件,这里只需要编译setup.s和bootsect.s这两个文件,在这里需要更改tools/build.c文件,若不更改则会出现下述错误。错误的原因是在make BootImage时并没有传递system参数,所以argv[3]是none,那么只能够将build.c文件内有关argv[3]的代码块注释掉。

操作系统实验01-操作系统的引导

将tools/build.c文件做出如下更改

/*
 *  linux/tools/build.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * This file builds a disk-image from three different files:
 *
 * - bootsect: max 510 bytes of 8086 machine code, loads the rest
 * - setup: max 4 sectors of 8086 machine code, sets up system parm
 * - system: 80386 code for actual system
 *
 * It does some checking that all files are of the correct type, and
 * just writes the result to stdout, removing headers and padding to
 * the right amount. It also writes some system data to stderr.
 */

/*
 * Changes by tytso to allow root device specification
 */

#include <stdio.h>	/* fprintf */
#include <string.h>
#include <stdlib.h>	/* contains exit */
#include <sys/types.h>	/* unistd.h needs this */
#include <sys/stat.h>
#include <linux/fs.h>
#include <unistd.h>	/* contains read/write */
#include <fcntl.h>

/*
 * Changes by falcon<zhangjinw@gmail.com> to define MAJOR and MINOR for they
 * are not defined in current linux header file linux/fs.h,I copy it from
 * include/linux/fs.h directly.
 */

#ifndef MAJOR
	#define MAJOR(a) (((unsigned)(a))>>8)
#endif
#ifndef MINOR
	#define MINOR(a) ((a)&0xff)
#endif

#define MINIX_HEADER 32
#define GCC_HEADER 1024

#define SYS_SIZE 0x3000

/*
 * Changes by falcon<zhangjinw@gmail.com> to let this kernel Image file boot
 * with a root image file on the first hardware device /dev/hd1, hence, you
 * should prepare a root image file, and configure the bochs with
 * the following lines(please set the ... as suitable info):
 * 	...
 *      floppya: 1_44="Image", status=inserted
 *      ata0-master: type=disk, path="/path/to/rootimage.img", mode=flat ...
 *      ...
 */

#define DEFAULT_MAJOR_ROOT 3
#define DEFAULT_MINOR_ROOT 1

/* max nr of sectors of setup: don't change unless you also change
 * bootsect etc */
#define SETUP_SECTS 4

#define STRINGIFY(x) #x

void die(char * str)
{
	fprintf(stderr,"%s\n",str);
	exit(1);
}

void usage(void)
{
	die("Usage: build bootsect setup system [rootdev] [> image]");
}

int main(int argc, char ** argv)
{
	int i,c,id;
	char buf[1024];
	char major_root, minor_root;
	struct stat sb;

	if ((argc != 4) && (argc != 5))
		usage();
	if (argc == 5) {
		if (strcmp(argv[4], "FLOPPY")) {
			if (stat(argv[4], &sb)) {
				perror(argv[4]);
				die("Couldn't stat root device.");
			}
			major_root = MAJOR(sb.st_rdev);
			minor_root = MINOR(sb.st_rdev);
		} else {
			major_root = 0;
			minor_root = 0;
		}
	} else {
		major_root = DEFAULT_MAJOR_ROOT;
		minor_root = DEFAULT_MINOR_ROOT;
	}
	fprintf(stderr, "Root device is (%d, %d)\n", major_root, minor_root);
	if ((major_root != 2) && (major_root != 3) &&
	    (major_root != 0)) {
		fprintf(stderr, "Illegal root device (major = %d)\n",
			major_root);
		die("Bad root device --- major #");
	}
	for (i=0;i<sizeof buf; i++) buf[i]=0;
	if ((id=open(argv[1],O_RDONLY,0))<0)
		die("Unable to open 'boot'");
	if (read(id,buf,MINIX_HEADER) != MINIX_HEADER)
		die("Unable to read header of 'boot'");
	if (((long *) buf)[0]!=0x04100301)
		die("Non-Minix header of 'boot'");
	if (((long *) buf)[1]!=MINIX_HEADER)
		die("Non-Minix header of 'boot'");
	if (((long *) buf)[3]!=0)
		die("Illegal data segment in 'boot'");
	if (((long *) buf)[4]!=0)
		die("Illegal bss in 'boot'");
	if (((long *) buf)[5] != 0)
		die("Non-Minix header of 'boot'");
	if (((long *) buf)[7] != 0)
		die("Illegal symbol table in 'boot'");
	i=read(id,buf,sizeof buf);
	fprintf(stderr,"Boot sector %d bytes.\n",i);
	if (i != 512)
		die("Boot block must be exactly 512 bytes");
	if ((*(unsigned short *)(buf+510)) != 0xAA55)
		die("Boot block hasn't got boot flag (0xAA55)");
	buf[508] = (char) minor_root;
	buf[509] = (char) major_root;	
	i=write(1,buf,512);
	if (i!=512)
		die("Write call failed");
	close (id);
	
	if ((id=open(argv[2],O_RDONLY,0))<0)
		die("Unable to open 'setup'");
	if (read(id,buf,MINIX_HEADER) != MINIX_HEADER)
		die("Unable to read header of 'setup'");
	if (((long *) buf)[0]!=0x04100301)
		die("Non-Minix header of 'setup'");
	if (((long *) buf)[1]!=MINIX_HEADER)
		die("Non-Minix header of 'setup'");
	if (((long *) buf)[3]!=0)
		die("Illegal data segment in 'setup'");
	if (((long *) buf)[4]!=0)
		die("Illegal bss in 'setup'");
	if (((long *) buf)[5] != 0)
		die("Non-Minix header of 'setup'");
	if (((long *) buf)[7] != 0)
		die("Illegal symbol table in 'setup'");
	for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
		if (write(1,buf,c)!=c)
			die("Write call failed");
	close (id);
	if (i > SETUP_SECTS*512)
		die("Setup exceeds " STRINGIFY(SETUP_SECTS)
			" sectors - rewrite build/boot/setup");
	fprintf(stderr,"Setup is %d bytes.\n",i);
	for (c=0 ; c<sizeof(buf) ; c++)
		buf[c] = '\0';
	while (i<SETUP_SECTS*512) {
		c = SETUP_SECTS*512-i;
		if (c > sizeof(buf))
			c = sizeof(buf);
		if (write(1,buf,c) != c)
			die("Write call failed");
		i += c;
	}
	
//	if ((id=open(argv[3],O_RDONLY,0))<0)
//		die("Unable to open 'system'");
//	if (read(id,buf,GCC_HEADER) != GCC_HEADER)
//		die("Unable to read header of 'system'");
//	if (((long *) buf)[5] != 0)
//		die("Non-GCC header of 'system'");
//	for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
//		if (write(1,buf,c)!=c)
//			die("Write call failed");
//	close(id);
//	fprintf(stderr,"System is %d bytes.\n",i);
//	if (i > SYS_SIZE*16)
//		die("System is too big");
	return(0);
}

编译运行

make BootImage
../run

运行效果如下所示

操作系统实验01-操作系统的引导

问题回答

当PC的电源打开后,80x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,这个地址通常是ROM—BIOS中的地址。PC机的BIOS将执行某些系统的检测,并在物理地址0处开始初始化中断向量。此后将启动设备的第一个扇区512字节读入内存绝对地址0x7C00处。因为当时system模块的长度不会超过0x80000字节大小512KB,所以bootsect程序把system模块读入物理地址0x10000开始位置处时并不会覆盖在0x90000处开始的bootsect和setup模块,多此一举的是system模块移到内存中相对靠后的位置,以便加载系统主模块。解决方案是在保证操作系统启动引导成功的前提下尽量扩大ROM—BIOS的内存寻址范围。

相关文章: