MIT 6.828 操作系统工程 2018 fall lab1 part3 内核 笔记 and 中文注释源代码阅读

mit 6.828 lab 代码和笔记,以及中文注释源代码已放置在github中:
https://github.com/yunwei37/xv6-labs

Part 3: The Kernel 内核

使用虚拟内存解决位置依赖性

内核的链接地址(由objdump打印)与加载地址之间存在(相当大的)差异;操作系统内核通常喜欢被链接并在很高的虚拟地址(例如0xf0100000)上运行,以便将处理器虚拟地址空间的下部留给用户程序使用。

  • 链接地址 f0100000
  • 加载地址 00100000

许多机器在地址0xf0100000上没有任何物理内存,因此我们不能指望能够在其中存储内核;将使用处理器的内存管理硬件将虚拟地址0xf0100000(内核代码期望在其上运行的链接地址)映射到物理地址0x00100000(引导加载程序将内核加载到物理内存中)。

这样,尽管内核的虚拟地址足够高,可以为用户进程留出足够的地址空间,但是它将被加载到PC RAM中1MB点的BIOS ROM上方的物理内存中。

在这个阶段中,仅映射前4MB的物理内存;

映射:kern/entrypgdir.c 中手写,静态初始化的页面目录和页面表。
直到kern / entry.S设置了CR0_PG标志,内存引用才被视为物理地址。

  • 将范围从0xf0000000到0xf0400000的虚拟地址转换为物理地址0x00000000到0x00400000

  • 将虚拟地址0x00000000到0x00400000转换为物理地址0x00000000到0x00400000

  • kern/entrypgdir.c:

#include <inc/mmu.h>
#include <inc/memlayout.h>

pte_t entry_pgtable[NPTENTRIES];

// entry.S页面目录从虚拟地址KERNBASE开始映射前4MB的物理内存
// (也就是说,它映射虚拟地址
// 地址[KERNBASE,KERNBASE + 4MB)到物理地址[0,4MB)
// 我们选择4MB,因为这就是我们可以在一页的空间中映射的表
// 这足以使我们完成启动的早期阶段。我们也映射
// 虚拟地址[0,4MB)到物理地址[0,4MB)这个
// 区域对于entry.S中的一些指令至关重要,然后我们
// 不再使用它。
//
// 页面目录(和页面表)必须从页面边界开始,
// 因此是“ __aligned__”属性。 另外,由于限制
// 与链接和静态初始化程序有关, 我们在这里使用“ x + PTE_P”
// 而不是更标准的“ x | PTE_P”。  其他地方
// 您应该使用“ |”组合标志。
__attribute__((__aligned__(PGSIZE)))
pde_t entry_pgdir[NPDENTRIES] = {
   
	// 将VA的[0,4MB)映射到PA的[0,4MB)
	[0]
		= ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P,
	// 将VA的[KERNBASE,KERNBASE + 4MB)映射到PA的[0,4MB)
	[KERNBASE>>PDXSHIFT]
		= ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P + PTE_W
};

// 页表的条目0映射到物理页0,条目1映射到
// 物理页面1,依此类推
__attribute__((__aligned__(PGSIZE)))
pte_t entry_pgtable[NPTENTRIES] = {
   
	0x000000 | PTE_P | PTE_W,
	0x001000 | PTE_P | PTE_W,
	0x002000 | PTE_P | PTE_W,
	0x003000 | PTE_P | PTE_W,
	0x004000 | PTE_P | PTE_W,
	0x005000 | PTE_P | PTE_W,
  ................

  • kern/entry.S
/* See COPYRIGHT for copyright information. */

#include <inc/mmu.h>
#include <inc/memlayout.h>

# 逻辑右移
#define SRL(val, shamt)		(((val) >> (shamt)) & ~(-1 << (32 - (shamt))))


###################################################################
# 内核(此代码)链接到地址〜(KERNBASE + 1 Meg),
# 但引导加载程序会将其加载到地址〜1 Meg。
#	
# RELOC(x)将符号x从其链接地址映射到其在
# 物理内存中的实际位置(其加载地址)。	 
###################################################################

#define	RELOC(x) ((x) - KERNBASE)

#define MULTIBOOT_HEADER_MAGIC (0x1BADB002)
#define MULTIBOOT_HEADER_FLAGS (0)
#define CHECKSUM (-(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS))

###################################################################
# 进入点
###################################################################

.text

# Multiboot标头
.align 4
.long MULTIBOOT_HEADER_MAGIC
.long MULTIBOOT_HEADER_FLAGS
.long CHECKSUM

# '_start'指定ELF入口点。  既然当引导程序进入此代码时我们还没设置
# 虚拟内存,我们需要
# bootloader跳到入口点的*物理*地址。
.globl		_start
_start = RELOC(entry)

.globl entry
entry:
	movw	$0x1234,0x472			# 热启动

	# 我们尚未设置虚拟内存, 因此我们从
	# 引导加载程序加载内核的物理地址为:1MB
	# (加上几个字节)处开始运行.  但是,C代码被链接为在
	# KERNBASE+1MB 的位置运行。  我们建立了一个简单的页面目录,
	# 将虚拟地址[KERNBASE,KERNBASE + 4MB)转换为
	# 物理地址[0,4MB)。  这4MB区域
	# 直到我们在实验2 mem_init中设置真实页面表为止
	# 是足够的。

	# 将entry_pgdir的物理地址加载到cr3中。   entry_pgdir
	# 在entrypgdir.c中定义。
	movl	$(RELOC(entry_pgdir)), %eax
	movl	%eax, %cr3
	# 打开分页功能。
	movl	%cr0, %eax
	orl	$(CR0_PE|CR0_PG|CR0_WP), %eax
	movl	%eax, %cr0

	# 现在启用了分页,但是我们仍在低EIP上运行
	# (为什么这样可以?) 进入之前先跳到上方c代码中的
	# KERNBASE
	mov	$relocated, %eax
	jmp	*%eax
relocated:

	# 清除帧指针寄存器(EBP)
	# 这样,一旦我们调试C代码,
	# 堆栈回溯将正确终止。
	movl	$0x0,%ebp			# 空帧指针

	# 设置堆栈指针
	movl	$(bootstacktop),%esp

	# 现在转到C代码
	call	i386_init

	# 代码永远不会到这里,但如果到了,那就让它循环死机吧。
spin:	jmp	spin


.data
###################################################################
# 启动堆栈
###################################################################
	.p2align	PGSHIFT		# 页面对齐
	.globl		bootstack
bootstack:
	.space		KSTKSIZE
	.globl		bootstacktop   
bootstacktop:

不在这两个范围之一内的任何虚拟地址都将导致硬件异常:导致QEMU转储计算机状态并退出。

练习7:

使用QEMU和GDB跟踪到JOS内核并在movl %eax, %cr0处停止。检查0x00100000和0xf0100000的内存。现在,使用stepiGDB命令单步执行该指令。同样,检查内存为0x00100000和0xf0100000。

在movl %eax, %cr0处停止:

(gdb) x 0x00100000
   0x100000:	add    0x1bad(%eax),%dh
(gdb) x 0xf0100000
   0xf0100000 <_start-268435468>:	add    %al,(%eax)

si:

0x00100028 in ?? ()
(gdb) x 0x00100000
   0x100000:	add    0x1bad(%eax),%dh
(gdb) x 0xf0100000
   0xf0100000 <_start-268435468>:	add    0x1bad(%eax),%dh

建立新映射后 的第一条指令是:

mov $relocated, %eax

这时的eax是:

(gdb) info registers
eax 0xf010002f -267386833

格式化打印到控制台:

  • kern/printf.c

    内核的cprintf控制台输出的简单实现,
    基于printfmt()和内核控制台的cputchar()。

  • lib/printfmt.c

// 精简的基本printf样式格式化例程,
// 被printf,sprintf,fprintf等共同使用
// 内核和用户程序也使用此代码。

#include <inc/types.h>
#include <inc/stdio.h>
#include <inc/string.h>
#include <inc/stdarg.h>
#include <inc/error.h>

/*
 * 数字支持空格或零填充和字段宽度格式。
 * 
 *
 * 特殊格式%e带有整数错误代码
 * 并输出描述错误的字符串。
 * 整数可以是正数或负数,
 * ,使-E_NO_MEM和E_NO_MEM等效。
 */

static const char * const error_string[MAXERROR] =
{
   
	[E_UNSPECIFIED]	= "unspecified error",
	[E_BAD_ENV]	= "bad environment",
	[E_INVAL]	= "invalid parameter",
	[E_NO_MEM]	= "out of memory",
	[E_NO_FREE_ENV]	= "out of environments",
	[E_FAULT]	= "segmentation fault",
};

/*
 * 使用指定的putch函数和关联的指针putdat
 * 以相反的顺序打印数字(基数<= 16).
 */
static void
printnum(void (*putch)(int, void*), void *putdat,
	 unsigned long long num, unsigned base, 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值