Mit6.828lab1 part3

Part3 The kernel

我们已经知道引导加载程序的链接地址和加载地址是一致的,但是对于内核来说,则完全不同,内核的链接地址和加载地址是不一致的。

操作相同内核经常链接和运行在高的虚拟地址上,例如:0xf0100000,以便于把低的虚拟地址留给用户程序运行。

但是很多机器可能并没有0xf0100000那么高的物理地址空间,因此我们会使用处理器将0xf0100000的虚拟地址映射到0x00100000的物理地址上。

这样做的结果是虽然内核的虚拟地址在0xf0100000这么高的地址上,但实际加载时仅仅被加载到了0xf0100000的位置,刚好在BIOS ROM的上方。

movl %eax, %cr0

上诉语句实现虚拟地址到物理地址的映射。

Formatted Printing to the Console

浏览kern/printf.c, lib/printfmt.c和kern/console.c,理清楚它们间的关系。

printf.c调用了printfmt.c中的vprintfmt函数,printf.c调用了console.c的cputchar函数。总体来说printfmt.c和console.c提供给了printf.c可调用的函数,console.c直接与底层打交道,并在console.c中定义了字符集,如:
在这里插入图片描述
printfmt.c提供了各种打印样式,exercise8要求补全的代码段就在该文件中:
在这里插入图片描述

仿造上面的形式,即可以完成exercise8:
在这里插入图片描述
对exercise8后问题的回答:
1.print.c使用了console.c的哪一个函数?
答:使用了cputchar()函数。

2.解释console.c的如下代码段:

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      }

CRT_SIZE表示屏幕上可以显示的最大字符串数,当crt_pos>=CRT_SIZE时,表示需要翻页,memmove把原来1-79行的内容复制到0-78行,然后for循环的作用是将当前第79行清空,由此可以起到前进一行的效果。

3.在执行如下代码时

在这里插入图片描述
(1)当调用cprintf时,fmt指向的是什么内容,ap指向的是什么内容。
答:cprintf调用了vcprintf,而fmt和ap是vcprintf函数的两个参数。fmt指向的是显示信息的格式字符串,在这段代码中,它指向的就是"x %d, y %x, z %d\n"字符串。而ap是va_list类型,这个类型专门用来处理输入参数的个数是可变的情况,所以ap会指向所有输入参数的集合。

4.执行以下代码:

    unsigned int i = 0x00646c72;
    cprintf("H%x Wo%s", 57616, &i);

(1)输出是什么?并解释。
答:输出结果:输出结果
原因:%x表示以16进制形式输出,57616的16进制形式是e110,因此H%x输出结果为He110。%s是以字符串形式输出,&i的意思是i的地址。
假设i变量的地址为0x00,那么i的4个字节的值存放在0x00,0x01,0x02,0x03四处。由于是小端存储,所以0x00处存放0x72(‘r’),0x01处存放0x6c(‘l’),0x02处存放0x64(‘d’),0x03处存放0x00(’\0’).
所以cprintf将会从i的地址开始一个字节一个字节遍历,正好输出 “World”。
(2)x86本身是小端模式,如果是大端模式的话,怎样设置i才能得到相同的输出,是否需要将57616修改为其他的值?

答:若是大端模式的话,i应该设置为0x726c6400,而57616不需要修改为其他值,因为57616不是通过地址来访问的。

5.看下面的代码,在’y='后面会输出什么?为什么会这样?

 cprintf("x=%d y=%d", 3);

答:输出结果:
在这里插入图片描述
由于y参数没有被指定,所以会输出一个奇怪的数字。

The Stack

x86堆栈指针始终指向已使用堆栈的最低的位置(向下生长的),低于指针指向的地方全部是未使用过的堆栈空间。向堆栈中加入数据分成两步,第一步降低指针位置,从而使指针指向更低的地方,然后将数据写到当前指针指向的地方。弹栈也分为两步,第一步读取堆栈指针当前指向位置的值,第二步升高指针位置,使指针指向更高的地方。

exercise9:
1.操作系统是从哪一条指令开始初始化堆栈空间的?
答:esp,ebp寄存器与堆栈息息相关,因此只用分析那些操作esp,ebp寄存器的指令即可。在前面的学习中我们分析了main.c和boot.s文件,这两个文件是内核加载文件,执行结束后会到entry.s文件中,这两个文件中并没有对esp和ebp寄存器进行操作的语句。因此我们对entry.s文件进行分析:

在文件的最后有如下语句:

在这里插入图片描述
call i386_init语句已经进行了内核初始化的工作,因此在该语句之前就已经进行了堆栈的初始化,在该语句之前正好有两句语句对esp和ebp寄存器进行了操作:
在这里插入图片描述
结合注释我们可以知道这两个语句就是我们要找的初始化堆栈空间的语句。

2.这个堆栈在内存的什么地方?
答:堆栈的地址空间为 0xf0108000-0xf0110000,其中栈顶指针指向0xf0110000.

3.内核是如何给堆栈保留一块内存空间的?
答:在entry.s文件最后定义了boot stack,在bootstack中定义了space的大小等于KSTKSIZE。

4.堆栈指针又是指向这块被保留的区域的哪一端的呢?
答:堆栈指针指向顶端,因为把bootstacktop赋给了esp。

对esp,ebp,eip的简单理解:
esp是存的栈顶的地址,ebp存的是栈底的地址而eip存的是要执行的下一条指令的地址。

1.EIP寄存器里存储的是CPU下次要执行的指令的地址。也就是调用完fun函数后,让CPU知道应该执行main函数中的printf(“函数调用结束”)语句了。

2.EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。等到调用结束,EBP会把其地址再次传回给ESP。所以ESP又一次指向了函数调用结束后,栈顶的地址。

3.ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值