Environment
环境搭建
环境
内核版本:4.4.72
busybox版本:1.32.1
gcc版本:5.4.0
虚拟机操作系统:ubuntu16.04 64位
依赖安装
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc qemu qemu-systemsudo apt-get install bison flex libncurses5-dev
gcc版本切换
看情况切换gcc;
sudo apt-get install -y gcc-x.x # x.x为版本sudo apt-get install -y g++-x.x# 重新建立软连接cd /usr/bin #进入/usr/bin文件夹下sudo rm -r gcc #移除之前的软连接sudo ln -sf gcc-x.x gcc #建立gcc4.7的软连接sudo rm -r g++ #同gccsudo ln -sf g++-x.x g++
内核源码下载与编译
清华库https: ...
basic_knowledge
Basic Knowledge
Kernel Basic
kernel也是一个程序:
用来管理软件发出的数据I/O要求;
将要求转义为指令;
交给CPU和计算机中其他组件处理;
kernel 最主要的功能有两点:
控制并与硬件进行交互
提供 application 能运行的环境
Ring Model
intel CPU 将 CPU 的特权级别分为 4 个级别:Ring 0, Ring 1, Ring 2, Ring 3。
Ring0 只给 OS 使用,Ring 3 所有程序都可以使用,内层 Ring 可以随便使用外层 Ring 的资源。
使用 Ring Model 是为了提升系统安全性,例如某个间谍软件作为一个在 Ring 3 运行的用户程序,在不通知用户的时候打开摄像头会被阻止,因为访问硬件需要使用 being 驱动程序保留的 Ring 1 的方法。
大多数的现代操作系统只使用了 Ring 0 和 Ring 3。
Loadable Kernel Modules(LKMs)
可加载核心模块(内核模块)就像运行在内核空间的可执行程序,包括:
驱动程序(Device driver ...
challenge6.2:init_IDT
challenge6.2:init IDT
编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */void idt_init(void){ /* LAB1 YOUR CODE : STEP 2 */ /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? * __ ...
challenge6.3:interrupt_clock
challenge6.3:interrupt clock
完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。
先看一下print_ticks子程序;
#define TICK_NUM 100static void print_ticks(){ cprintf("%d ticks\n", TICK_NUM); // 打印了TICK_NUM变量#ifdef DEBUG_GRADE cprintf("End of Test.\n"); panic("EOT: kernel seems ok.");#endif}
再看一下kern/driver/clock.c:
#include <x86.h>#include <trap.h>#include <stdio.h>#include <p ...
challenge6.1:Interrupt_Descriptor_Table
challenge6.1:Interrupt Descriptor Table
中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
中断描述符表(Interrupt Descriptor Table) 中断描述符表把每个中断或异常编号和一个指向中断服务例程的描述符联系起来。同GDT一样,IDT是一个8字节的描述符数组,但IDT的第一项可以包含一个描述符。CPU把中断(异常)号乘以8做为IDT的索引 。IDT可以位于内存的任意位置 ,CPU通过IDT寄存器(IDTR)的内容来寻址IDT的起始地址 。
因此中断描述符表的每一个表项占用8bytes;
操作系统在IDT中设置好各种中断向量对应的中断描述符,把每个中断或异常编号和一个指向中断服务例程的描述符。当产生中断时,根据指引,找到中断服务例程的描述符,最开始2个字节和最末尾2个字节定义了offset,第16-31位定义了处理代码入口地址的段选择子,由此找到GDT的base_addr,加上offset,即可得到中断处理代码的入口;
challenge5:trace_stackframe
challenge5:trace stack frame
完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址,通过编译make qemu来理解输出结果;
在完成编译后:
查看lab1/obj/bootblock.asm,了解bootloader源码与机器码的语句和地址等的对应关系;
查看lab1/obj/kernel.asm,了解 ucore OS源码与机器码的语句和地址等的对应关系。
函数调用栈
在kern/debug/kdebug.c中,存在需要填补的print_stackframe()函数:
/* * * print_stackframe - print a list of the saved eip values from the nested 'call' * instructions that led to the current point of execution * * The x86 stack pointer, namely esp, po ...
challenge4:load_kernel_elf
challenge4:load kernel ELF
通过阅读bootmain.c,了解bootloader如何加载ELF文件。通过分析源代码和通过qemu来运行并调试bootloader&OS,
bootloader如何读取硬盘扇区的?
bootloader是如何加载ELF格式的OS?
提示:可阅读“硬盘访问概述”,“ELF执行文件格式概述”这两小节。
读取硬盘扇区
CPU进入保护模式后,bootLoader会从硬盘上加载并运行OS,考虑到实现的简单性,bootloader的访问硬盘都是LBA模式的PIO(Program IO)方式,即所有的IO操作是通过CPU访问硬盘的IO地址寄存器完成。
已知,硬盘数据存储在硬盘扇区中,一个扇区大小为512bytes,来看boot/bootmain.c中和读取硬盘数据的相关代码;
// x86.h// inb(port) 获取端口数据static inline uint8_t inb(uint16_t port) __attribute__((always_inline));// outb(port, data) 对端口输出数 ...
challenge3:go_into_protected_mode
challenge3:go into protected mode
BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。分析bootloader是如何完成从实模式进入保护模式的。
提示:需要阅读**小节“保护模式和分段机制”**和lab1/boot/bootasm.S源码,了解如何从实模式切换到保护模式,需要了解:
为何开启A20,以及如何开启A20
如何初始化GDT表
如何使能和进入保护模式
bootLoader开始运作
初始化
首先进入bootasm.S中,从0x7c00(BIOS加载bootLoader到内存0x7c00处)开始执行命令;
# start address should be 0:7c00, in real mode, the beginning address of the running bootloader.globl startstart:.code16 # Assemble for 16-bit mode cl ...
challenge2.4:costomize_breakpoint
challenge2.4:costomize breakpoint
找一个bootloader或内核中的代码位置,设置断点并进行测试。
随便断在0x7c08看看;
gdbinit:
file bin/kerneltarget remote :1234break *0x7c08define hook-stopx/i $pcend
调试结果
(gdb) ni=> 0x7c0a: in $0x64,%al0x00007c0a in ?? ()=> 0x7c0c: test $0x2,%al0x00007c0c in ?? ()=> 0x7c0e: jne 0x7c0a0x00007c0e in ?? ()
challenge2.3:diff_asm
challenge2.3:diff asm
从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
设置gdbinit,使得调试时能够强制反汇编每一个断点的汇编代码;
file bin/kerneltarget remote :1234break *0x7c00define hook-stopx/i $pcend
在make debug后,可以看到断点停留在0x7c00,使用ni观察并记录汇编代码;
reakpoint 1, 0x00007c00 in ?? ()(gdb) ni=> 0x7c01: cld0x00007c01 in ?? ()=> 0x7c02: xor %eax,%eax0x00007c02 in ?? ()=> 0x7c04: mov %eax,%ds0x00007c04 in ?? ()=> 0x7c06: mov %eax,%es0x00007c06 in ?? ()=> 0x7c08: mov ...