MIT 6.828 (一) Lab 1: Booting a PC

获取完资源就开始干了。
我这里就做了个大概的内容分析,具体的还是跟着那几个大佬吧

前面一大堆都是没用的,就是告诉一些作业的提交。

Part 1: PC Bootstrap

这个并没有要你做啥,就是让你 熟悉下汇编。

然后让你知道怎么运行那个内核的,这些都是些不用讲的东西,看看就行了。
说一下,这个内核退出说是ctrl+a x,意思是先按ctrl+a,再按x

然后就开始来重点了,第一个是让你知道现在计算机内存的分布。

The PC’s Physical Address Space(PC的物理地址空间)

在这里插入图片描述

The ROM BIOS

后面就比较直接了,直接让你去运行这个内核,一步步来看他是怎么运行的。

在这里插入图片描述
就是让你这样运行两个终端,跑GDB 调试,第一个运行make qemu-gdb,第二个运行make gdb
不出意外,在make gdb 里面出现的第一条指令是跳转指令。
在这里插入图片描述
跳到了哪里去,这个地址官方是给了你解释的,就是段地址*16加偏移地纸,很显然,他的地址是在BIOS中。
然后回让你一步步执行看发生了什么。
不难发现,计算机的运行最先开始的是 BIOS。

后面一步步,执行的内容我就看不懂,只知道他就做了一些初始化工作,想了解的去看看大佬的博客吧。

Part 2: The Boot Loader

初始化完成BIOS 之后,就运行Boot Loader,就是引导操作系统,这个东西一直都是放在计算机磁盘的第一个扇区(也用可能是其他的引导,这个百度搜一搜能搜出来,另外一个最常见的就是U盘启动,学计算机的重装系统至少也有十次八次了吧,所以这个很容易理解,我们重装系统的步骤不就是现在BIOS里面选了U盘引导,干的就是这个了)。然后BIOS会把这个运行的引导程序装入到内存0x7c00-0x7dff。我不知道现代操作系统是不是这,但是这个系统是的。
这个引导系统主要就干了两件事:

  1. 实模式转换成保护模式,区别百度一下有讲解的,另外发现了另一个操作系统学习资源,好像文档还挺齐全的。
  2. 引导加载程序通过x86的特殊I / O指令直接访问IDE磁盘设备寄存器,从而从硬盘读取内核。

后面会让你去看源码,知道怎么运行的Boot Loader,这个各位就自己翻翻博客吧,我就不细讲了,贴个自己的大致介绍。

#include <inc/mmu.h>

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.set PROT_MODE_CSEG, 0x8         # kernel code segment selector
.set PROT_MODE_DSEG, 0x10        # kernel data segment selector
.set CR0_PE_ON,      0x1         # protected mode enable flag

.globl start
start:			      # 开始程序首先关了中断,和串处理操作的移动指针(这个暂时不用管)
  .code16                     # Assemble for 16-bit mode
  cli                         # Disable interrupts
  cld                         # String operations increment
#初始化  数据段 扩展段  和 栈 的段寄存器 用于保护模式
  # Set up the important data segment registers (DS, ES, SS).
  xorw    %ax,%ax             # Segment number zero
  movw    %ax,%ds             # -> Data Segment
  movw    %ax,%es             # -> Extra Segment
  movw    %ax,%ss             # -> Stack Segment

  # Enable A20:
  #   For backwards compatibility with the earliest PCs, physical
  #   address line 20 is tied low, so that addresses higher than
  #   1MB wrap around to zero by default.  This code undoes this.
seta20.1:  #开启A20为了兼容 低版本的 处理器
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

  movb    $0xd1,%al               # 0xd1 -> port 0x64
  outb    %al,$0x64

seta20.2:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.2

  movb    $0xdf,%al               # 0xdf -> port 0x60
  outb    %al,$0x60

  # Switch from real to protected mode, using a bootstrap GDT
  # and segment translation that makes virtual addresses 
  # identical to their physical addresses, so that the 
  # effective memory map does not change during the switch.
  lgdt    gdtdesc 		# 存放 GDT表信息
  movl    %cr0, %eax		# 用或操作 把最后一位置1  开启保护模式
  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0
  
  # Jump to next instruction, but in 32-bit code segment.
  # Switches processor into 32-bit mode.
  ljmp    $PROT_MODE_CSEG, $protcseg	# 这个时候跳到了 32模式下了

  .code32                     # Assemble for 32-bit mode
protcseg:	# 这个修改一些寄存器的值,具体做了啥也不清楚 应该是规定的一些操作
  # Set up the protected-mode data segment registers
  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
  # 简单来讲 上面 就是初始化了一些寄存器,然后就去 boot main 里面了
  # Set up the stack pointer and call into C.
  movl    $start, %esp
  call bootmain

  # If bootmain returns (it shouldn't), loop.
spin:	# 这个翻译上面英文就好
  jmp spin

# Bootstrap GDT
.p2align 2                                # force 4 byte alignment
gdt:	# GDT 表信息,具体的不清楚
  SEG_NULL				# null seg
  SEG(STA_X|STA_R, 0x0, 0xffffffff)	# code seg
  SEG(STA_W, 0x0, 0xffffffff)	        # data seg

gdtdesc:
  .word   0x17                            # sizeof(gdt) - 1
  .long   gdt                             # address gdt

由上面这注释,应该很容易得出他问的问题的答案。

后面有一个让你运行一个C语言指针程序,那个有点基础应该就看得懂。就不多说了。

main.c里面,主要是加载ELF文件,也就是你的操作系统,什么是ELF,这个可以在百度百科里面了解到。

boot main.c

#include <inc/x86.h>
#include <inc/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(boot.S and main.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 boot.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.
 **********************************************************************/

#define SECTSIZE	512
#define ELFHDR		((struct Elf *) 0x10000) // scratch space

void readsect(void*, uint32_t);
void readseg(uint32_t, uint32_t, uint32_t);

void
bootmain(void)
{
	struct Proghdr *ph, *eph;

	// read 1st page off disk 把内核的起始地址第一个页加载到内存,ELFHDR处 一页为 512*8=4M
	readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);//一般是操作系统映象文件的elf 头部

	// is this a valid ELF?  是一个ELF文件 就继续否侧失败
	if (ELFHDR->e_magic != ELF_MAGIC)
		goto bad;
	//加载 程序表头到 ph
	// load each program segment (ignores ph flags)
	ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
	eph = ph + ELFHDR->e_phnum;//这个是 表未  e_phnum 存的是表项个数
	for (; ph < eph; ph++) //这个就是把表里面的都加载到内存
		// p_pa is the load address of this segment (as well
		// as the physical address)
		readseg(ph->p_pa, ph->p_memsz, ph->p_offset);

	// call the entry point from the ELF header
	// note: does not return!
	((void (*)(void)) (ELFHDR->e_entry))();//e_entry 是程序运行的入口 也就是在这个时候 操作系统开始加载了

bad:	//具体干啥的  我不知道 如果没猜错 就是执行 没有加载到系统的操作,
	outw(0x8A00, 0x8A00);
	outw(0x8A00, 0x8E00);
	while (1)
		/* do nothing */;
}

// Read 'count' bytes at 'offset' from kernel into physical address 'pa'.
// Might copy more than asked  
// 顾名思义 ,就是加载 偏移量为 offset 的 连续  count 个字节 到地址 pa
void
readseg(uint32_t pa, uint32_t count, uint32_t offset)
{
	uint32_t end_pa;

	end_pa = pa + count;

	// round down to sector boundary
	pa &= ~(SECTSIZE - 1);

	// translate from bytes to sectors, and kernel starts at sector 1
	offset = (offset / SECTSIZE) + 1;

	// If this is too slow, we could read lots of sectors at a time.
	// We'd write more to memory than asked, but it doesn't matter --
	// we load in increasing order.
	while (pa < end_pa) {
		// Since we haven't enabled paging yet and we're using
		// an identity segment mapping (see boot.S), we can
		// use physical addresses directly.  This won't be the
		// case once JOS enables the MMU.
		readsect((uint8_t*) pa, offset);
		pa += SECTSIZE;
		offset++;
	}
}

void
waitdisk(void)
{	// 判磁盘是不是准备 好了      
	// wait for disk reaady
	while ((inb(0x1F7) & 0xC0) != 0x40)
		/* do nothing */;
}

void
readsect(void *dst, uint32_t offset)
{
	// wait for disk to be ready
	waitdisk();

	outb(0x1F2, 1);		// count = 1
	outb(0x1F3, offset);
	outb(0x1F4, offset >> 8);
	outb(0x1F5, offset >> 16);
	outb(0x1F6, (offset >> 24) | 0xE0);
	outb(0x1F7, 0x20);	// cmd 0x20 - read sectors

	// wait for disk to be ready
	waitdisk();

	// read a sector 读一个扇区
	insl(0x1F0, dst, SECTSIZE/4);
}

后面几步还是要自己好好坐一坐,我没有做什么详细的介绍。
一如既往大佬博客,像我这样的菜鸡是无法理解的。

最后让你做个测试,运行Boot Loader 前看下0x00100000处的8个内存字,运行后再看一下,发现原本是 全是 0,后面就有了一大堆乱七八糟的值。这个不是很明显吗,看我上面main.c的注释,明显有个函数把硬盘里面的值读到了内存。

Part 3: The Kernel

后面的实验,这个大佬的GitHub就全都有了。

我做个简单的笔记。

movl %eax, %cr0

这条语句实现了,映射,将高地址映射到了物理地址的低地址,这是因为计算机希望操作系统是运行在高地址,用户运行在低地址,详细内容,下一次实验会讲清楚。

练习8 让你完善那个printf里面的%o也就是8进制输出。

这个很容易了,把前面16进制输出复制一遍就行了,不做详细介绍了。

然后你需要回答4个问题

  1. printf之间的关系依旧找大佬的博客
1      if (crt_pos >= CRT_SIZE) {
2              int i;
3              memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
4              for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
5                      crt_buf[i] = 0x0700 | ' ';
6              crt_pos -= CRT_COLS;
7      }

解释这个,看起来就是如果超过了一页,就把最前面的那一行删掉??,然后继续输出下一行???不知道理解对没。

  1. 这个是个重点 ,这个实验很容易看出来C语言压栈的顺序是从后往前压栈。
  2. 这个不说了,就是按格式输出
  3. 这个就是,我们经常用的printf,如果少了一个参数会咋样,通过看内存应该就知道,会输出一个随机值。多一个多的其实就没啥用。
  4. 这个就是压栈是从后往前压,所以可以实现,多个参数。如果是从前往后压该怎么办?我还不知道怎么处理。

练习9 问啥时候初始化堆栈,这个看看大佬的博客就行.
练习10 这个就是让你明白是怎么调用函数的,又是怎么返回的。

一般来说,栈基地址指向的是栈底,栈指针指向的是栈顶。
在这里插入图片描述
大概调用一个函数就是这么干的,怎么返回的就不用我说了吧。
调用参数也就是通过栈来的,如果超过了五个参数我也不知道会发生啥,反正这上面没说。
练习 11 也就是让你写个输出栈里面的内容。

练习 12 让你实现一个调试信息的指令。
改三个地方。
前两个都在kern/monitor.c
第一个

static struct Command commands[] = {
          { "help", "Display this list of commands", mon_help },
          { "kerninfo", "Display information about the kernel", mon_kerninfo },
          {"backtrace","Display stack backtrace", mon_backtrace},
 };

后面那个是多加的,前面那两个很眼熟吧。

int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
        // Your code here.
        //      return 0;
        uint32_t ebp,eip,*p;
        struct Eipdebuginfo info;
        ebp=read_ebp();
        while(ebp!=0){
                p=(uint32_t*)ebp;
                eip=p[1];
                cprintf("ebp %x eip %x args %08x %08x %08x %08x %08x\n",ebp,p[1],p[2],p[3],p[4],p[5],p[6]);
                if(debuginfo_eip(eip,&info)==0){
                        int fn_offset=eip-info.eip_fn_addr;
                        cprintf("%s:%d:%.*s+%d\n",info.eip_file,info.eip_line,info.eip_fn_namelen,info.eip_fn_name,fn_offset);
                }
                ebp=p[0];
        }
        return 0;
}

这个也很眼熟吧,前面改过。
到这个地方实际上已经能够运行了,只是没有行号。

最后一个在kern/kdebug.c

         stab_binsearch(stabs, &lfun, &rfun, N_SLINE, addr - info->eip_fn_add    r);
 
         if (lfun <= rfun)
         {
                 info->eip_line = stabs[lfun].n_desc;
         }

这个具体原理我也不知道,抄别人的。百度能搜到
到此,实验一就全部完成了,目前我能了解的就这么多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值