ucore Lab0 一些杂记

前一阵子开始做 MIT 6.828,做了两三个实验才发现清华的 ucore 貌似更友好一些,再加上前几个实验也与6.828 有所重叠,于是决定迁移阵地。

文章计划分两类,一类是代码的分析,另一类是实验的解答和比较。

1. 计算机执行第一条指令之前,分段状态是怎样的?

执行make debug, 然后考察 QEMU monitor 中 GDT 的值:

GDT= 00000000 0000ffff

参考 GDTR 寄存器:

参考手册 2.4.1 节描述:

On power up or reset of the processor, the base address is set to the default value of 0 and the limit is set to 0FFFFH. A new base address must be loaded into the GDTR as part of the processor initialization process for protected-mode operation.

结论 计算机执行第一条指令前,也就是重置状态,全局描述符表区域被默认设置为,基址=0,limit=0FFFFH, 似乎是把整个内存空间都视作GDT,其本质上没有分段.

2. 怎样验证生成的磁盘文件是合法的 elf 文件?

期望: 磁盘的第 510 个(倒数第二个)字节是 0x55, 第 511 个(倒数第一个)字节是 0xAA.

验证:

cd ~/ucore_os_lab/labcodes_answer/lab1_result
make $(call totarget,ucore.img)

输出结果:

//省略。..
000001e0 05 42 86 03 83 04 00 00 00 00 01 00 00 02 00 00 |.B..............|
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa <-得证 |..............U.|
//省略。..

原理 sign.c用于将一个二进制文件构建为一个磁盘文件。

3. bootloader 被如何加载到内存中?

前置问题 bootloader 如何生成?

考察 Makefile,参考实验报告中关于 make 的流程可知,ucore.imgbootblockkernel合并而成.bootloader ,有效的 elf 文件是 /obj/bootblock.o; kernel 的有效 elf 文件是 bin/kernel.

BIOS把磁盘的第一个扇区作为 bootloader,即把磁盘的前 512 字节加载进来.

4. kernel 希望自己怎样加载到内存中,提供了哪些信息?

考察 kernel:

执行readelf -a kernel|less:

其中由

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x00100000 0x00100000 0x0eb0b 0x0eb0b R E 0x1000
LOAD 0x010000 0x0010f000 0x0010f000 0x00a16 0x01d80 RW 0x1000

可知 kernel 提供了两个 program header,与section header对照,并结合链接脚本可知 kernel 提供的链接信息如下:

5. bootasm.S 中切换到保护模式之后,GDT 的分布是怎样的?

参考手册 2.4.1 节,GDTR 寄存器长度是 48bit, 32 位模式下维护着 GDT 的线性基址和字节数量。

LGDTSGDT 分别用于(从程序中)加载(至 cpu) 和(从 CPU) 保存(到程序)中。cpu 重置时,GDT基址默认为 0,limit 默认为0FFFFH. 初始化保护模式时必须设置新的基址。

所以源代码中的 gdtdesc 就是描述了 GDT 的位置和字节数。但注意字节数=size(GDT)-1, 因为 (3.5.1)GDT 中的第一个条目不被使用,即"null descriptor". 当 segement selector 指向此条目时,不会产生异常,而是产生通用保护错误。

每个 segment descriptor 是 32*2=64 bit.

汇编代码初始化了代码段和数据段:

gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel

SEG_ASM的定义是#define SEG_ASM(type,base,lim).

首先是 type.

type 的定义可参考手册 3.4.5.1 节:

代码中事实上给出了相应的宏:

//mmu.h
#define STA_X 0x8 // Executable segment
#define STA_E 0x4 // Expand down (non-executable segments)
#define STA_C 0x4 // Conforming code segment (executable only)
#define STA_W 0x2 // Writeable (non-executable segments)
#define STA_R 0x2 // Readable (executable segments)
#define STA_A 0x1 // Accessed

于是代码段的类型为可执行|可读取,数据段的类型为可读写。

代码初始阶段我们将内存设置为平铺模型(参考手册 3.2.2 节):

所以 base 和 limit 设置为内存边界值。

而 gdt 的大小,即为 64bit * 3 = 8byte * 3 = 24byte

24-1=23=0x17. 至于为何要-1, 参考 OSdev 的解释。

验证 gdt 的内存分布:

首先 qemu 进入 monitor (Ctrl-Alt 2):

//查看 gdtr:
info registers
GDT = 00007c54 00000017

即 gdt 基址为 0x7c54, 大小为 0x17. 与代码中一致。

通过 gdb 查看:

make debug # 进入保护模式
(gdb) p gdt
$1 = {<text variable, no debug info>} 0x7c54 <gdt> # 得到 gdt 的地址
(gdb) x/6x 0x7c54
0x7c54 <gdt>: 0x00000000 0x00000000 0x0000ffff 0x00cf9a00
0x7c64 <gdt+16>: 0x0000ffff 0x00cf9300

用逻辑表来描述主要字段就是:

Index Base Limit 类型
0 0 0 NULL
1 0x0 0xffffffff code
2 0x0 0xffffffff data

6. 要执行保护模式下的 c 代码,需要如何设置寄存器?

执行call bootmain的前提是要把段寄存器设置正确,因为 call 标号 等价于

push IP
jmp near ptr 标号

通过长转移指令,修改 cs 和 ip.

ljmp $PROT_MODE_CSEG, $protcseg

即段内转移。那么bootmain位于哪个段里?

其实我们设置 GDT 已经很清楚了,代码段和数据段都是从 0 开始!但是在保护模式下,ljmp 的第一个操作数不再是段地址,而是段选择子,即 GDT 的索引值。那么基于我们刚刚建立的 gdt,代码段和数据段的索引分别是1 和 2.所以只需将 cs 段选择子中的索引部分设置为 1 和 2 即可.

关于段选择子的格式,参考手册 3.4.3 节:

高位是索引,手动写入,低位由 cpu 自动写入.

再考察 Segment Selector 的格式(3.4.2 节):

Segment Selector 的三个字段分别是 Index,TI,RPL.

Index: 3~15bit,选择GDT 或 IDT 中8192 个条目之一,注意此 index 不是地址,而是索引号,从 0 开始每次增长 1,所以要想正确找到 gdt 条目的话还需*8.因为每个 segment descriptor 的大小是 64bit=8byte.

计算过程图:

图自 Understanding the LINUX KERNEL, 3rd edition

TI: Table Indicator, 指明是 GDT 还是 LDT,0 为 GDT,1 位 LDT.

RPL: Request Privilege Level.对于向内核的请求,此值为 0.

所以:用于描述 code 段的 selector 应该是 index 为 1,ti 为 0,RPL = 0,即 100b=0x8;用于描述 data 段的 selector 应该是 1000b=0x10.

7. 函数调用分析:

  1. 0 到多个 push,参数入栈
  2. 一个 call 指令. call 指令其实也 push 了返回地址,即 call 指令下一个命令的指令

函数序言:

保存并更新段基址.

pushl   %ebp
movl %esp , %ebp

所以在执行调用函数的代码前,已经有 1)参数 2)返回地址 3)ebp 三种类型的值入栈:

+|  栈底方向     | 高位地址
| ... |
| ... |
| 参数3 |
| 参数2 |
| 参数1 |
| 返回地址 |
| 上一层[ebp] | <-------- [ebp]
| 局部变量 | 低位地址

注意!当前 ebp 指向的值就是上一层函数的 ebp!

则有:

地址 代码
第一个参数(假定4byte) ss:[ebp+8]
返回地址 ss:[ebp+4]
上一层 ebp ss:[ebp]
第一个局部变量 ss:[ebp-4]

参考链接

lab1 练习 6 如何初始化中断向量表?

中段描述符表(IDT):

参考手册 6-11:

三种 gate 通过 type 指定类型.

对于每个描述符,都要按照宏#define SETGATE(gate, istrap, sel, off, dpl)填充其是否是 trap gate,以及指向段的 selector,limit,dpl.

通过中断访问门进而访问代码段,通过内存访问数据段.

本来特权级低的代码是不能访问特权级高的代码段(内核态)的,但是通过等级更低的门就可以了,门的特殊功效就是通向更高级别的段!这就是所谓的系统调用.

## ucore Lab0 一些杂记的更多相关文章

  1. [Erlang 0118] Erlang 杂记 V

       我在知乎回答问题不多,这个问题: "对你职业生涯帮助最大的习惯是什么?它是如何帮助你的?",我还是主动回答了一下.    做笔记 一开始笔记软件做的不好的时候就发邮件给自己, ...

  2. Ubuntu杂记——Ubuntu下用虚拟机共享上网

    由于最近把自己电脑环境换成了Ubuntu,但学校的网络是电信的闪讯,大学里用过的人都知道这货有多坑,而且没有Linux客户端,上网都是问题,怪不得国内用Linux的人那么少,特别是高校的学生(让我瞎逼 ...

  3. 一个ubuntu phper的自我修养(杂记)

    ubuntu使用杂记 1.flatabulous安装使用. flatabulous是一个ubuntu图标主题. 使用它,必须得安装tweak插件. sudo add-apt-repository pp ...

  4. 有关Java的日期处理的一些杂记

    在企业应用开发中,经常会遇到日期的相关处理,说实话JDK自带的日期方法很难用.就我个人而言我一般都会采用joda-time来替代JDK自身的日期. 这篇文章是杂记,所以写的比较零散,希望大家不要见怪. ...

  5. 分布式系统之CAP理论杂记[转]

    分布式系统之CAP理论杂记 http://www.cnblogs.com/highriver/archive/2011/09/15/2176833.html 分布式系统的CAP理论: 理论首先把分布式 ...

  6. Redis杂记

    参考资料: Redis 教程 | 菜鸟教程 : http://www.runoob.com/redis/redis-tutorial.html Redis快速入门 :http://www.yiibai ...

  7. MySQL杂记

    参考资料: w3school  SQL 教程 : http://www.w3school.com.cn/sql/index.asp 21分钟 MySQL 入门教程 : http://www.cnblo ...

  8. Android之开发杂记(一)

    1.cygwin环境变量设置 可在Cygwin.bat 中设置 set NDK_ROOT=P:/android/android-ndk-r8e 或者在home\Administrator\.bash_ ...

  9. ios程序开发杂记

    ios程序开发杂记 一.程序构建 与一般的程序构建无太大区别,都是源文件编译链接这一套,通常是在mac上做交叉编译,也就是利用xcode里带的ios编译工具集去生成arm架构的ios程序(或是x86的 ...

随机推荐

  1. linux grep 正则

    grep : 显示匹配行 -v: 反显示 -e 使用扩展正则表达式 黑色字体表明是原生正则表达式 红色字体表明是扩张正则表达式 1.匹配操作符 \: 转义字符串(正则使用扩展字符操作  没有使用-e ...

  2. legend3---Homestead中Laravel项目502 Bad Gateway

    legend3---Homestead中Laravel项目502 Bad Gateway 一.总结 一句话总结: 用查看错误日志的方法解决错误:(/var/log/nginx/.log) 1.home ...

  3. legend3---3、lavarel页面post请求错误之后跳转

    legend3---3.lavarel页面post请求错误之后跳转 一.总结 一句话总结: 控制器:return back()->withInput()->with('error','验证 ...

  4. SVN更新报错:Checksum mismatch for ……

    问题: Checksum mismatch while updating '……'; expected: '3f9fd4dd7d1a0304d8020f73300a3e07', actual: 'cd ...

  5. 线对 Line pairs、LP(分辨率cy/mm)

    线对 (Line pairs) 是胶片.镜头等电影摄影领域的专用名词. 每毫米线对一般指分辨率的单位,指仪器在一毫米内能分辨出多少对线. 在一定尺度内的可分辨线对数常被用来衡量仪器的空间分辨能力,能分 ...

  6. 软件-客户端管理工具-SourceTree:百科

    ylbtech-软件-客户端管理工具-SourceTree:百科 SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端管理工具,同时也是Mn版本控制系统工具 ...

  7. 基于Python对象引用、可变性和垃圾回收详解

    基于Python对象引用.可变性和垃圾回收详解 下面小编就为大家带来一篇基于Python对象引用.可变性和垃圾回收详解.小编觉得挺不错的,现在就分享给大家,也给大家做个参考. 变量不是盒子 在示例所示 ...

  8. 【DSP开发】【Linux开发】基于ARM+DSP进行应用开发

    针对当前应用的复杂性,SOC芯片更好能能满足应用和媒体的需求,集成众多接口,用ARM做为应用处理器进行多样化的应用开发和用户界面和接口,利用DSP进行算法加速,特别是媒体的编解码算法加速,既能够保持算 ...

  9. OpenCV在ARM-linux上的移植过程遇到的问题3---共享库中嵌套库居然带路径【未解决】

    [Linux开发]OpenCV在ARM-linux上的移植过程遇到的问题3-共享库中嵌套库居然带路径[未解决] 标签(空格分隔): [Linux开发] 移植opencv到tq2440 一.下载open ...

  10. 第六周学习总结&(实验报告四)

    一.实验目的 (1)掌握类的继承方法 (2)变量的继承和覆盖,方法的继承,重载和覆盖实现 二.实验内容 一.实验目的 (1)掌握类的继承 (2)变量的继承和覆盖,方法的继承,重载和覆盖的实现: 二.实 ...