# create ucore.img
UCOREIMG := $(call totarget,ucore.img)
$(UCOREIMG): $(kernel) $(bootblock)
$(V)dd if=/dev/zero of=$@ count=10000
$(V)dd if=$(bootblock) of=$@ conv=notrunc
$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc
$(call create_target,ucore.img)
dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。$@是输出文件名
第一行dd:就是开辟一块存储区域大小为10000 * 512字节然后将/dev/zero (0)输入进去【初始化一块区域】
第二行dd:读取bootblock(512字节)内容然后输出到$@ 转换格式为 notrunc(不截短输出文件)
第三行dd:读取kernel内容然后输出到$@ 转换格式同上。 注意有偏移seek=1 偏移 1 * 512 位置
有关dd命令可以查看linux下的dd命令使用详解 - 知乎 (zhihu.com)
说白了就是弄一块磁盘区域来把kernel和bootblock按照头512字节是bootblock然后其次是kernel放进去,在模拟的时候这个文件要放到指定磁盘位置,然后BLOS启动时会读取磁盘上该文件的内容,bootblock作为bootloader引导代码然后加载kernel了
# create kernel target
kernel = $(call totarget,kernel)
$(kernel): tools/kernel.ld
$(kernel): $(KOBJS)
@echo + ld $@
$(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)
@$(OBJDUMP) -S $@ > $(call asmfile,kernel)
@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)
$(call create_target,kernel)
kernel赋值为"bin/kernel"
执行toos/kernel.ld链接脚本
编译kernel\下的所有.s和.c文件
# create bootblock
bootfiles = $(call listf_cc,boot)
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
#编译boot\下的所有C文件
bootblock = $(call totarget,bootblock)
$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)
@echo + ld $@
$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)
@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)
@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)
@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)
$(call create_target,bootblock)
#编译boot\下所有文件,并链接bootblock文件
# create 'sign' tools
$(call add_files_host,tools/sign.c,sign,sign)
$(call create_target_host,sign,sign)
#前面需要sign这个,这里就是编译,tools/sign.c并调用
发现前面都用到了$(call totarget,…) totarget定义在tools/function.mk
totarget = $(addprefix $(BINDIR)$(SLASH),$(1))
#BINDIR在别处定义为"bin",slash定义为斜线"/",$(1)指代输入参数
#所以totarget作用为给输入参数添加前缀"bin/"
阅读sign.c可知,大小小于512字节且以0X55AA结尾
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
为了兼容早期版本初始化时物理地址只能寻址20位也就是1M,所以要开启A20这样物理寻址就能达到32位也就是4G
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
ljmp $PROT_MODE_CSEG, $protcseg
call bootmain
先从磁盘开始处读取了1页(8个扇区,每个512byte)的数据到内存64K处,再校验头部标识符是否合法.
接着从磁盘中读取每个程序段,并放到虚拟内存对应位置.
最后执行ELF入口程序,将控制权交给kernel
考察的是对EBP寄存器的运用.几乎所有本地编译器都会在每个函数体之前插入类似如下的汇编指令:
pushl %ebp
movl %esp,%ebp
这两条汇编指令的含义是:首先将ebp寄存器入栈,然后将栈顶指针esp赋值给ebp。“mov ebp esp”这条指令表面上看是用esp覆盖ebp原来的值,其实不然。因为给ebp赋值之前,原ebp值已经被压栈(位于栈顶),而新的ebp又恰恰指向栈顶。此时ebp寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原ebp入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的ebp值。
一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层ebp值。由于ebp中的地址处总是“上一层函数调用时的ebp值”,而在每一层函数调用中,都能通过当时的ebp值“向上(栈底方向)”能获取返回地址、参数值,“向下(栈顶方向)”能获取函数局部变量值。如此形成递归,直至到达栈底。这就是函数调用栈。
由文档给出的不难理解,双层循环内层循环复则打印,外层循环复则查看堆栈调用,关键代码如下:
eip = ((uint32_t *)ebp)[1];
ebp = ((uint32_t *)ebp)[0];
/* Gate descriptors for interrupts and traps */
struct gatedesc {
unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment
unsigned gd_ss : 16; // segment selector
unsigned gd_args : 5; // # args, 0 for interrupt/trap gates
unsigned gd_rsv1 : 3; // reserved(should be zero I guess)
unsigned gd_type : 4; // type(STS_{TG,IG32,TG32})
unsigned gd_s : 1; // must be 0 (system)
unsigned gd_dpl : 2; // descriptor(meaning new) privilege level
unsigned gd_p : 1; // Present
unsigned gd_off_31_16 : 16; // high bits of offset in segment
};
可以看出一个表项为8字节(64位)其中0 ~ 15位和48 ~ 63位分别为偏移量的低16位和高16位,两者拼接为偏移量,16~31位为段选择器。通过段选择子去GDT中找到对应的基地址,然后基地址加上偏移量就是中断处理程序的地址。
extern uintptr_t __vectors[];
int i ;
for( i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i++ ){
SETGATE(idt[i] , 0 , GD_KTEXT , __vectors[i] , DPL_KERNEL );
}
/**
* 【注意】除了系统调用中断(T_SYSCALL)使用陷阱门描述符且权限为用户态权限以外
* ,其它中断均使用特权级(DPL)为0的中断门描述符,权限为内核态权限;而ucore的
* 应用程序处于特权级3,需要采用`int 0x80`指令操作(这种方式称为软中断,软件
* 中断,Tra中断,在lab5会碰到)来发出系统调用请求,并要能实现从特权级3到特权
* 级0的转换,所以系统调用中断(T_SYSCALL)所对应的中断门描述符中的特权级(DPL)
* 需要设置为3。
*/
SETGATE(idt[T_SYSCALL] , 1 , GD_KTEXT , __vectors[T_SYSCALL] , DPL_USER);
// this is different than answer
lidt(&idt_pd);
就是找一些宏去填就行了,找宏很麻烦,其他还好
Too Simple? Yes, I think so!
/* LAB1 YOUR CODE : STEP 3 */
/* handle the timer interrupt */
/* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c
* (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks().
* (3) Too Simple? Yes, I think so!
*/
ticks++;
if(ticks % TICK_NUM == 0){
print_ticks();
}
break;