操作系统 ucore lab1

操作系统 ucore lab1

实验目的

操作系统是一个软件,也需要通过某种机制加载并运行它。在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。lab1提供了一个非常小的bootloader和ucore OS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。通过分析和实现这个bootloader和ucore OS,我们的目的在于了解到:

计算机原理

  • CPU的编址与寻址: 基于分段机制的内存管理
  • CPU的中断机制
  • 外设:串口/并口/CGA,时钟,硬盘

Bootloader软件

  • 编译运行bootloader的过程
  • 调试bootloader的方法
  • PC启动bootloader的过程
  • ELF执行文件的格式和加载
  • 外设访问:读硬盘,在CGA上显示字符串

ucore OS软件

  • 编译运行ucore OS的过程
  • ucore OS的启动过程
  • 调试ucore OS的方法
  • 函数调用关系:在汇编级了解函数调用栈的结构和处理过程
  • 中断管理:与软件相关的中断处理
  • 外设管理:时钟

实验步骤

练习1 理解通过make生成执行文件的过程
问题1:
  • 操作系统镜像文件ucore.img是如何一步一步生成的?
实验过程:
  • 先进入文件路径:

home/moocos/ucore_lab/labcodes_answer/lab1_result/

  • 执行make:

make “V=”

  • 观察过程
    (1)通过GCC编译器将Kernel目录下的.c文件编译成OBJ目录下的.o文件。
+ cc kern/init/init.c //编译init.c
gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o
+ cc kern/libs/readline.c //编译readline.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o
+ cc kern/libs/stdio.c //编译stdlio.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o
+ cc kern/debug/kdebug.c //编译kdebug.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o
+ cc kern/debug/kmonitor.c //编译komnitor.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o
+ cc kern/debug/panic.c //编译panic.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o
+ cc kern/driver/clock.c //编译clock.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o
+ cc kern/driver/console.c //编译console.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o
+ cc kern/driver/intr.c //编译intr.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o
+ cc kern/driver/picirq.c //编译prcirq.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o
+ cc kern/trap/trap.c //编译trap.c
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o
+ cc kern/trap/trapentry.S //编译trapentry.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o
+ cc kern/trap/vectors.S //编译vectors.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o
+ cc kern/mm/pmm.c //编译pmm.c
gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o
+ cc libs/printfmt.c //编译printfmt.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/printfmt.c -o obj/libs/printfmt.o
+ cc libs/string.c //编译string.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/string.c -o obj/libs/string.o

(2)ld命令根据链接脚本文件kernel.ld将生成的*.o文件,链接成BIN目录下的kernel文件。

+ ld bin/kernel //链接成kernel
ld -m    elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel  obj/kern/init/init.o obj/kern/libs/readline.o obj/kern/libs/stdio.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/debug/panic.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/intr.o obj/kern/driver/picirq.o obj/kern/trap/trap.o obj/kern/trap/trapentry.o obj/kern/trap/vectors.o obj/kern/mm/pmm.o  obj/libs/printfmt.o obj/libs/string.o

(3)通过GCC编译器将boot目录下的.c,.S文件以及tools目录下的sign.c文件编译成OBJ目录下的*.o文件。

+ cc boot/bootasm.S //编译bootasm.S
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
+ cc boot/bootmain.c //编译bootmain.c
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
+ cc tools/sign.c //编译sign.c
gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o
gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign

(4)ld命令将生成的*.o文件,链接成BIN目录下的bootblock文件。

+ ld bin/bootblock  //根据sign规范生成bootblock
ld -m    elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
'obj/bootblock.out' size: 488 bytes
build 512 bytes boot sector: 'bin/bootblock' success!

(5)dd命令将dev/zero, bin/bootblock,bin/kernel 写入到bin/ucore.img

//创建大小为10000个块的ucore.img,初始化为0,每个块为512字节
dd if=/dev/zero of=bin/ucore.img count=10000
10000+0 records in 
10000+0 records out 
5120000 bytes (5.1 MB) copied, 0.0242234 s, 211 MB/s
//把bootblock中的内容写到第一个块
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.0001916 s, 2.7 MB/s
//从第二个块开始写kernel中的内容
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
146+1 records in
146+1 records out
74923 bytes (75 kB) copied, 0.000380543 s, 197 MB/s
问题2:
  • 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
实验过程:
  • 大小为512字节
  • 多余的空间填0
  • 第510个(倒数第二个)字节是0x55,
  • 第511个(倒数第一个)字节是0xAA。
    在这里插入图片描述
    在这里插入图片描述
练习2
练习2.1 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行

(1)进入~/moocos/ucore_lab/labcodes/lab1/bin,即ucore.img所在文件夹。
(2)输入指令:

qemu -S -s -hda ucore.img -monitor stdio

在这里插入图片描述
(3)打开gdb。
在这里插入图片描述
(4)输入命令,使gdb与qemu通过1234端口进行通信,qemu会停止状态听从gbd命令。

target remote 127.0.0.1:1234

在这里插入图片描述
(5)输入命令,单步跟踪。

si

在这里插入图片描述
(6)查看BIOS代码

x/2i $pc

在这里插入图片描述

练习2.2 在初始化位置0x7c00 设置实地址断点,测试断点正常

(1)输入命令,设置断点。

b *0x7c00

(2)continue后,测试断点。

c
x/2i $pc

在这里插入图片描述

练习2.3 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较

(1)单步跟踪。
输入两次si。
在这里插入图片描述
(2)使用meld对比bootasm.S和bootlock.asm的代码

meld /home/moocos/moocos/ucore_lab/labcodes/lab1/boot/bootasm.S /home/moocos/moocos/ucore_lab/labcodes/lab1/obj/bootblock.asm

是相同的。
在这里插入图片描述

练习2.4 自己找一个bootloader或内核中的代码位置,设置断点并进行测试

(1)查看bootloader,选择0x7c06位置。

b *0x7c06
c
x/2i $pc

在这里插入图片描述

练习3:分析bootloader进入保护模式的过程
关中断和清除数据段寄存器
.globl start
start:
.code16                                             # 使用16位模式编译
    cli                                             # 禁用中断
    cld                                             # 清除方向标志
    # 建立重要的数据段寄存器(DS,ES,SS)。
    xorw %ax, %ax                                   # ax清0
    movw %ax, %ds                                   # ds清0
    movw %ax, %es                                   # es清0
    movw %ax, %ss                                   # ss清0
为何开启A20,以及如何开启A20
seta20.1:											# 等待8042输入缓冲区空
    inb $0x64, %al                                  # 从0x64端口中读入一个字节到al中
    testb $0x2, %al									# 测试al的第2位
    jnz seta20.1									# al的第2位为0,则跳出循环

    movb $0xd1, %al                                 # 将0xd1写入al中
    outb %al, $0x64                                 # 将0xd1写入到0x64端口中

seta20.2:											# 等待8042输入缓冲区空
    inb $0x64, %al                                  # 从0x64端口中读入一个字节到al中
    testb $0x2, %al									# 测试al的第2位
    jnz seta20.2									# al的第2位为0,则跳出循环

    movb $0xdf, %al                                 # 将0xdf入al中
    outb %al, $0x60                                 # 将0xdf入到0x64端口中,打开A20
如何初始化GDT表
  • 载入GDT表
 lgdt gdtdesc										# 载入GDT表
  • 进入保护模式
    cro的第0位为1表示处于保护模式
    movl %cr0, %eax									# 加载cro到eax
    orl $CR0_PE_ON, %eax							# 将eax的第0位置为1
    movl %eax, %cr0									# 将cr0的第0位置为1
  • 通过长跳转更新cs的基地址
ljmp $PROT_MODE_CSEG, $protcseg 					# $PROT_MODE_CSEG的值为0x80
  • 设置段寄存器,并建立堆栈
.code32                                             # 使用32位模式编译
protcseg:
    movw $PROT_MODE_DSEG, %ax                       # ax赋0x80
    movw %ax, %ds                                   # ds赋0x80
    movw %ax, %es                                   # es赋0x80
    movw %ax, %fs                                   # fs赋0x80
    movw %ax, %gs                                   # gs赋0x80
    movw %ax, %ss                                   # ss赋0x80
    movl $0x0, %ebp									# 设置帧指针
    movl $start, %esp								# 设置栈指针
  • 转到保护模式完成,进入boot主方法。
    call bootmain
如何使能和进入保护模式
  • 将cr0寄存器置1,cro的第0位为1表示处于保护模式。
练习4:分析bootloader加载ELF格式的OS的过程
bootloader如何读取硬盘扇区
  • 等待磁盘准备好;
  • 发出读取扇区的命令;
  • 等待磁盘准备好;
  • 把磁盘扇区数据读到指定内存。

在这里插入图片描述

bootmain.c:

#include <defs.h>
#include <x86.h>
#include <elf.h>
/* *********************************************************************
 * This a dirt simple boot loader, whose sole job is to boot
 * an ELF kernel image from the first IDE hard disk.
 *
 * DISK LAYOUT
 *  * This program(bootasm.S and bootmain.c) is the bootloader.
 *    It should be stored in the first sector of the disk.
 *
 *  * The 2nd sector onward holds the kernel image.
 *
 *  * The kernel image must be in ELF format.
 *
 * BOOT UP STEPS
 *  * when the CPU boots it loads the BIOS into memory and executes it
 *
 *  * the BIOS intializes devices, sets of the interrupt routines, and
 *    reads the first sector of the boot device(e.g., hard-drive)
 *    into memory and jumps to it.
 *
 *  * Assuming this boot loader is stored in the first sector of the
 *    hard-drive, this code takes over...
 *
 *  * control starts in bootasm.S -- which sets up protected mode,
 *    and a stack so C code then run, then calls bootmain()
 *
 *  * bootmain() in this file takes over, reads in the kernel and jumps to it.
 * */

/* waitdisk - wait for disk ready */
static void
waitdisk(void) { //如果0x1F7的最高2位是01,跳出循环
    while ((inb(0x1F7) & 0xC0) != 0x40)
        /* do nothing */;
}
/* 读节 - 将“secno”处的单个扇区读入“dst” */
static void
readsect(void *dst, uint32_t secno) {
    // 等待磁盘准备就绪
    waitdisk();
    // 用LBA模式的PIO(Program IO)方式来访问硬盘
    outb(0x1F2, 1); //读取一个扇区
    outb(0x1F3, secno & 0xFF); //要读取的扇区编号
    outb(0x1F4, (secno >> 8) & 0xFF); //用来存放读写柱面的低8位字节 
    outb(0x1F5, (secno >> 16) & 0xFF); //用来存放读写柱面的高2位字节
    outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); // 用来存放要读/写的磁盘号及磁头号
    outb(0x1F7, 0x20);                      // cmd 0x20-读为扇区
    // 等待磁盘准备就绪
    waitdisk();
    // 读一个扇区
    insl(0x1F0, dst, SECTSIZE / 4); //获取数据
}

/* *
 * readseg - read @count bytes at @offset from kernel into virtual address @va,might copy more than asked.
 * */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
    uintptr_t end_va = va + count;
    // 转至分区边界
    va -= offset % SECTSIZE;
    // 从字节转换到扇区;内核从扇区1开始
    uint32_t secno = (offset / SECTSIZE) + 1; //加1因为0扇区被引导占用
    // 如果速度太慢,我们一次就能读到很多扇区。
    // 我们会写更多的记忆,而不是要求,但这并不重要--我们以增序加载。
    for (; va < end_va; va += SECTSIZE, secno ++) {
        readsect((void *)va, secno);
    }
}
bootloader如何加载ELF格式的OS
  • 从硬盘读了8个扇区数据到内存0x10000处,并把这里强制转换成elfhdr使用。
  • 校验e_magic字段。
  • 根据偏移量分别把程序段的数据读取到内存中。

ElF结构定义

struct elfhdr {
    uint32_t e_magic;     // 判断读出来的ELF格式的文件是否为正确的格式
    uint8_t e_elf[12];
    uint16_t e_type;      // 1=可重定位,2=可执行,3=共享对象,4=核心映像
    uint16_t e_machine;   // 3=x86,4=68K等.
    uint32_t e_version;   // 文件版本,总是1
    uint32_t e_entry;     // 程序入口所对应的虚拟地址。
    uint32_t e_phoff;     // 程序头表的位置偏移
    uint32_t e_shoff;     // 区段标题或0的文件位置
    uint32_t e_flags;     // 特定于体系结构的标志,通常为0
    uint16_t e_ehsize;    // 这个elf头的大小
    uint16_t e_phentsize; // 程序头中条目的大小
    uint16_t e_phnum;     // 程序头表中的入口数目
    uint16_t e_shentsize; // 节标题中条目的大小
    uint16_t e_shnum;     // 节标题中的条目数或0
    uint16_t e_shstrndx;  // 包含节名称字符串的节号。
};

bootmain.c:

#define SECTSIZE        512
#define ELFHDR          ((struct elfhdr *)0x10000) 
/* 引导-引导加载器的条目 */
void
bootmain(void) {
    // 从磁盘上读取第一页
    readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
    // 这是有效的ELF吗?
    if (ELFHDR->e_magic != ELF_MAGIC) { // 通过储存在头部的幻数判断是否是合法的ELF文件
        goto bad;
    }
    struct proghdr *ph, *eph;
    // 加载每个程序段(忽略ph标志)
    ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); // 先将描述表的头地址存在ph
    eph = ph + ELFHDR->e_phnum;
    // 按照描述表将ELF文件中数据载入内存
    for (; ph < eph; ph ++) {
        readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
    }// ELF文件0x1000位置后面的0xd1ec比特被载入内存0x00100000,ELF文件0xf000位置后面的0x1d20比特被载入内存0x0010e000
    // 从ELF报头调用入口点
    // 注:不返回
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);
    /* do nothing */
    while (1);
}
练习5:实现函数调用堆栈跟踪函数

栈结构:
在这里插入图片描述
ebp表示上一层。
eip表示第一个局部变量。
实现函数:

print_stackframe(void) {
	int i,j;
	uint32_t ebp=read_ebp();
	uint32_t eip=read_eip();
    for(i=0;i<STACKFRAME_DEPTH&&ebp!=0;i++){
    	cprintf("ebp:0x%08x eip:0x%08x\n",ebp,eip); 
        uint32_t *args=(uint32_t *)ebp+2;
		cprintf("参数:");
        for(j=0;j<4;j++){
            cprintf("0x%08x ", args[j]);
        }
        cprintf("\n");
        print_debuginfo(eip-1);
        eip=((uint32_t *)ebp)[1];
        ebp=((uint32_t *)ebp)[0];
	}
}

直接涉及函数:

  • kdebug.c中read_eip():
read_eip(void) {
    uint32_t eip;
    asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); //内联汇编,读取(ebp-4)的值到变量eip
    return eip;
}
  • x86.h中read_ebp() :
static inline uint32_t
read_ebp(void) {
    uint32_t ebp;
    asm volatile ("movl %%ebp, %0" : "=r" (ebp)); //内联汇编,读取edp寄存器的值到变量ebp
    return ebp;
}
  • kdebug.c中print_debuginfo(uintptr_t eip):
void
print_debuginfo(uintptr_t eip) {
    struct eipdebuginfo info;
    if (debuginfo_eip(eip, &info) != 0) {
        cprintf("    <unknow>: -- 0x%08x --\n", eip);
    }
    else {
        char fnname[256];
        int j;
        for (j = 0; j < info.eip_fn_namelen; j ++) {
            fnname[j] = info.eip_fn_name[j];
        }
        fnname[j] = '\0';
        cprintf("    %s:%d: %s+%d\n", info.eip_file, info.eip_line,
                fnname, eip - info.eip_fn_addr);
    }
}

执行make qemu:

Special kernel symbols:
  entry  0x00100000 (phys)
  etext  0x001032cf (phys)
  edata  0x0010ea16 (phys)
  end    0x0010fd20 (phys)
Kernel executable memory footprint: 64KB
ebp:0x00007b08 eip:0x001009a6
参数:0x00010094 0x00000000 0x00007b38 0x00100092 
    kern/debug/kdebug.c:307: print_stackframe+21
ebp:0x00007b18 eip:0x00100ca1
参数:0x00000000 0x00000000 0x00000000 0x00007b88 
    kern/debug/kmonitor.c:125: mon_backtrace+10
ebp:0x00007b38 eip:0x00100092
参数:0x00000000 0x00007b60 0xffff0000 0x00007b64 
    kern/init/init.c:48: grade_backtrace2+33
ebp:0x00007b58 eip:0x001000bb
参数:0x00000000 0xffff0000 0x00007b84 0x00000029 
    kern/init/init.c:53: grade_backtrace1+38
ebp:0x00007b78 eip:0x001000d9
参数:0x00000000 0x00100000 0xffff0000 0x0000001d 
    kern/init/init.c:58: grade_backtrace0+23
ebp:0x00007b98 eip:0x001000fe
参数:0x001032fc 0x001032e0 0x0000130a 0x00000000 
    kern/init/init.c:63: grade_backtrace+34
ebp:0x00007bc8 eip:0x00100055
参数:0x00000000 0x00000000 0x00000000 0x00010094 
    kern/init/init.c:28: kern_init+84
ebp:0x00007bf8 eip:0x00007d68
参数:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8 
    <unknow>: -- 0x00007d67 --
++ setup timer interrupts
(THU.CST) os is loading ...

输出从顶向下,实际执行顺序相反,从bootmain开始一步步调用函数,info.eip_file、info.eip_line和fnname分别表示调用发生的文件、调用发生所在行和调用函数名。

练习6:完善中断初始化和处理 (需要编程)
中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

向量表结构:
在这里插入图片描述
由各项相加可知,一个表项的占64bit,即8字节,其中0 ~ 15位和48 ~ 63位分别为偏移量的低16位和高16位,两者拼接为偏移量,16~31位为段选择器。

  • 使用段选择符中的偏移值在GDT(全局描述符表) 或 LDT(局部描述符表)中定位相应的段描述符。
  • 利用段描述符校验段的访问权限和范围,以确保该段是可以访问的并且偏移量位于段界限内。
  • 利用段描述符中取得的段基地址加上偏移量,形成一个线性地址。
请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。
  • mmu.h中的SETGATE(gate,istrap,sel,off,dpl):
    在这里插入图片描述
  • lidt指令用于把内存中的限长值和基地址操作数加载到IDTR寄存器中。
  • idt_init()实现:
    在这里插入图片描述
  • make debug一下子:
    在这里插入图片描述
    在这里插入图片描述
请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数
  • 结构体trapframe:
struct trapframe {
    struct pushregs tf_regs;
    uint16_t tf_gs;
    uint16_t tf_padding0;
    uint16_t tf_fs;
    uint16_t tf_padding1;
    uint16_t tf_es;
    uint16_t tf_padding2;
    uint16_t tf_ds;
    uint16_t tf_padding3;
    uint32_t tf_trapno;
    /* below here defined by x86 hardware */
    uint32_t tf_err;
    uintptr_t tf_eip;
    uint16_t tf_cs;
    uint16_t tf_padding4;
    uint32_t tf_eflags;
    /* below here only when crossing rings, such as from user to kernel */
    uintptr_t tf_esp;
    uint16_t tf_ss;
    uint16_t tf_padding5;
} __attribute__((packed));
  • trap.c中的print_ticks():
static void print_ticks() {
    cprintf("%d ticks\n",TICK_NUM);
#ifdef DEBUG_GRADEvolatile size_t ticks;
    cprintf("End of Test.\n");
    panic("EOT: kernel seems ok.");
#endif
}
  • clock.h中的ticks:
volatile size_t ticks;
  • trap.c中的TICK_NUM:
#define TICK_NUM 100
  • 对trap_dispatch(struct trapframe *tf)的补充:
    在这里插入图片描述
  • make qemu一下子:
    在这里插入图片描述
扩展练习 Challenge 1(需要编程)

扩展proj4,增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务(通过网络查询所需信息,可找老师咨询。如果完成,且有兴趣做代替考试的实验,可找老师商量)。需写出详细的设计和分析报告。完成出色的可获得适当加分。
提示: 规范一下 challenge 的流程。
kern_init 调用 switch_test,该函数如下:
static void
switch_test(void) {
print_cur_status(); // print 当前 cs/ss/ds 等寄存器状态
cprintf("+++ switch to user mode +++\n");
switch_to_user(); // switch to user mode
print_cur_status();
cprintf("+++ switch to kernel mode +++\n");
switch_to_kernel(); // switch to kernel mode
print_cur_status();
}
switchto* 函数建议通过 中断处理的方式实现。主要要完成的代码是在 trap 里面处理 T_SWITCH_TO* 中断,并设置好返回的状态。
在 lab1 里面完成代码以后,执行 make grade 应该能够评测结果是否正确。

扩展练习 Challenge 2(需要编程)

用键盘实现用户模式内核模式切换。具体目标是:“键盘输入3时切换到用户模式,键盘输入0时切换到内核模式”。 基本思路是借鉴软中断(syscall功能)的代码,并且把trap.c中软中断处理的设置语句拿过来。
注意:

1.关于调试工具,不建议用lab1_print_cur_status()来显示,要注意到寄存器的值要在中断完成后tranentry.S里面iret结束的时候才写回,所以再trap.c里面不好观察,建议用print_trapframe(tf)
 2.关于内联汇编,最开始调试的时候,参数容易出现错误,可能的错误代码如下
asm volatile ( “sub $0x8, %%esp \n”
“int %0 \n”
“movl %%ebp, %%esp”
: )
要去掉参数int %0 \n这一行
3.软中断是利用了临时栈来处理的,所以有压栈和出栈的汇编语句。硬件中断本身就在内核态了,直接处理就可以了。

参考答案在mooc_os_lab中的mooc_os_2014 branch中的labcodes_answer/lab1_result目录下

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值