ucore lab1 实验报告

UCORE LAB1 实验报告

练习一 理解通过make生成执行文件的过程

1、操作系统镜像文件ucore.img是如何一步一步生成的?
先打开lab1文件夹下的Makefile,查看里面的代码,在各个函数里最终找到“create ucore.img ”的代码段。以下是其中各个命令的含义:

dd是一条linux命令指令:
dd:用指定大小的块拷贝文件,并在拷贝的同时进行指定的转换
if=文件名:输入文件名,缺省为标准输入 即指定源文件
of=文件名:输出文件名,缺省为标准输出 即指定目的文件
count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数
conv=conversion:用指定的参数转换文件

使用命令‘make V=’,查看具体都做了什么:
在这里插入图片描述

这里我们可以看到,先是拷贝了大小为10000的块的文件,然后把kernel和bootblock拷贝进入文件里,然后生成ucore.img
2、一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
进入lab1/tools/sign.c 查看代码
在这里插入图片描述
可以得到条件是符合规范的硬盘主引导扇区的大小为512个字节,且最后两个字节为0x55、0xAA。

练习二 使用qemu执行并调试lab1中的软件

1、从 CPU加电后执行的第一条指令开始,单步跟踪 BIOS的执行
开始操作前先把lab/tools/gdbinit/的内容更改成下图:
在这里插入图片描述
再在terminal终端里执行命令‘make debug’,然后会发现qemu停在了0x7c00,的位置:

因为我们在‘gdbinit’里设置了breakpoint,所以我们在运行到这个地址的时候会停住
2、从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较
使用命令si和 x/i $pc,可以一条条指令进行,并且查看对应的汇编指令:
在这里插入图片描述

使用si和 x/i $pc 指令一行一行的跟踪,将得到的反汇编代码为:

0x00007c01 in ?? ()
(gdb) x/i $pc
=> 0x7c01: cld
(gdb) si
0x00007c02 in ?? ()
(gdb) x/i $pc
=> 0x7c02: xor %eax,%eax
(gdb) si
0x00007c04 in ?? ()
(gdb) x/i $pc
=> 0x7c04: mov %eax,%ds

bootasm.C:
在这里插入图片描述

练习三 分析bootloader进入保护模式的过程

分析从bootloader进入保护模式的过程。BIOS 将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行 bootloader。请分析bootloader是如何完成从实模式进入保护模式的

我们来查看bootasm.s的源码

cli # Disable interrupts
cld # String operations increment
xorw %ax, %ax # Segment number zero
movw %ax, %ds # -> Data Segment
movw %ax, %es # -> Extra Segment
movw %ax, %ss # -> Stack Segment

首先操作系统将各个寄存器置为0

seta20.1:
inb $0x64, %al # 读取状态寄存器,等待8042键盘控制器闲置
testb $0x2, %al # 判断输入缓存是否为空
jnz seta20.1
movb $0xd1, %al # 0xd1表示写输出端口命令,参数随后通过0x60端口写入
outb %al, $0x64
seta20.2:
inb $0x64, %al
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 通过0x60写入数据11011111 即将A20置1
outb %al, $0x60

下一段代码是开启A20
首先我们介绍一下A20
启用A20:为了与最早的PC向后兼容,物理地址线20在低电平,因此地址高于1MB默认回零。 此代码撤消了此操作。使得全部的32位地址线可用。

lgdt gdtdesc
这一串代码是加载GDT表

movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
这一串代码是将CR0的第0位置1

movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
这里是将段寄存器重装

movl $0x0, %ebp
movl $start, %esp
call bootmain
这一段是进入bootmain继续下一步的执行

练习四 分析bootloader加载ELF格式的OS的过程

bootmain:

bootmain(void) {
readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
if (ELFHDR->e_magic != ELF_MAGIC)
{
goto bad;
}
struct proghdr *ph, *eph;
ph = (struct proghdr )((uintptr_t)ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph ++)
{
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
}
((void (
)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
while (1);
}

readsect从设备的第secno扇区读取数据到dst位置:

static void
readsect(void *dst, uint32_t secno) {
waitdisk();
outb(0x1F2, 1); // 设置读取扇区的数目为1
outb(0x1F3, secno & 0xFF);
outb(0x1F4, (secno >> 8) & 0xFF);
outb(0x1F5, (secno >> 16) & 0xFF);
outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
// 上面四条指令联合制定了扇区号
// 在这4个字节线联合构成的32位参数中
// 29-31位强制设为1
// 28位(=0)表示访问"Disk 0"
// 0-27位是28位的偏移量
outb(0x1F7, 0x20); // 0x20命令,读取扇区
waitdisk();
insl(0x1F0, dst, SECTSIZE / 4); // 读取到dst位置,幻数4因为这里以DW为单位
}
IO地址 功能
0x1f0 读数据,当0x1f7不为忙状态时,可以读。
0x1f2 要读写的扇区数,每次读写前,你需要表明你要读写几个扇区。最小是1个扇区
0x1f3 如果是LBA模式,就是LBA参数的0-7位
0x1f4 如果是LBA模式,就是LBA参数的8-15位
0x1f5 如果是LBA模式,就是LBA参数的16-23位
0x1f6 第0~3位:如果是LBA模式就是24-27位 第4位:为0主盘;为1从盘
0x1f7 状态和命令寄存器。操作时先给命令,再读取,如果不是忙状态就从0x1f0端口读数据

加载ELF文件
>bootmain(void) {

if (ELFHDR->e_magic != ELF_MAGIC) //这里有个判断
{
    goto bad;                 
}
struct proghdr *ph, *eph;

//ELF头部有描述ELF文件应加载到内存什么位置的描述表,这里读取出来将之存入ph
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;

//按照程序头表的描述,将ELF文件中的数据载入内存
for (1; ph < eph; ph ++) {
    readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);


((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();//根据ELF头表中的入口信息,找到内核的入口并开始运行
bad:
..........
}

练习五 实现函数调用堆栈跟踪函数

我的代码:

void print_stackframe(void)
{
uint32_t ebp=read_ebp();//(1) call read_ebp() to get the value of ebp. the type is (uint32_t)
uint32_t eip=read_eip();//(2) call read_eip() to get the value of eip. the type is (uint32_t)
int i;
for(i=0;i<STACKFRAME_DEPTH&&ebp!=0;i++){//(3) from 0 … STACKFRAME_DEPTH
cprintf(“ebp:0x%08x eip:0x%08x “,ebp,eip);//(3.1)printf value of ebp, eip
uint32_t tmp=(uint32_t )ebp+2;
cprintf(“arg :0x%08x 0x%08x 0x%08x 0x%08x”,
(tmp+0),
(tmp+1),(tmp+2),(tmp+3));//(3.2)(uint32_t)calling arguments [0…4] = the contents in address (unit32_t)ebp +2 [0…4]
cprintf(”\n”);//(3.3) cprintf("\n");
print_debuginfo(eip-1);//(3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.
eip=((uint32_t *)ebp)[1];
ebp=((uint32_t *)ebp)[0];//(3.5) popup a calling stackframe
}
}

练习六 完善中断初始化和处理

1、中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

中断描述符表一个表项占8字节。其中0-15位和48-63位分别为offset的低16位和高16位。16~31位为段选择子。通过段选择子获得段基址,加上段内偏移量即可得到中断处理代码的入口。

2、请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。

extern uintptr_t __vectors[];//声明__vertors[] You can use “extern uintptr_t __vectors[];” to define this extern variable which will be used later.
int i;
for(i=0;i<256;i++) {
SETGATE(idt[i],0,GD_KTEXT,__vectors[i],DPL_KERNEL); //对整个idt数组进行初始化
}
SETGATE(idt[T_SWITCH_TOK],0,GD_KTEXT,__vectors[T_SWITCH_TOK],DPL_USER);//在这里先把所有的中断都初始化为内核级的中断
lidt(&idt_pd);//使用lidt指令加载中断描述符表 just google it! and check the libs/x86.h to know more.利用google找到了相关函数
}

3、请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。



case IRQ_OFFSET + IRQ_TIMER:
ticks ++; //每一次时钟信号会使变量ticks加1
if (ticks==TICK_NUM) {//TICK_NUM已经被预定义成了100,每到100便调用print_ticks()函数打印
ticks-=TICK_NUM;
print_ticks();
}
break;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值