实验01-操作系统的引导

97 0

实验内容

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文件之后,直接在对应目录利用终端运行此脚本即可,结果如下

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]的代码块注释掉。

将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)&amp;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) &amp;&amp; (argc != 5))
		usage();
	if (argc == 5) {
		if (strcmp(argv[4], "FLOPPY")) {
			if (stat(argv[4], &amp;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) &amp;&amp; (major_root != 3) &amp;&amp;
	    (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 = '\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

运行效果如下所示

问题回答

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

发表评论

Scroll Up