北航操作系统实验Lab1
Exercise 1.1
- 修改交叉编译路径为
/OSLAB/compiler/usr/bin/mips_4KC-
gxemul/
目录下生成vmlinux
内核文件
Exercise 1.2
ELF(Executable and Linkable Format) 是一种文件格式,从名字上看它是可执行文件和可链接文件的格式。通常多个.c文件经过编译以后,生成多个.o文件(也即是目标文件), 这些.o文件就是ELF格式,然后链接器将多个.o链接到一起就得到了一个可执行文件。可执行文件也是ELF格式。
那么,ELF 文件中都包含有什么东西呢?简而言之,就是和程序相关的所有必要信息。具体来说包含5个部分。
- ELF Header,包括程序的基本信息,比如体系结构和操作系统,同时也包含了 Section Header Table 和 Program Header Table 相对文件的偏移量 (offset)。
- Program Header Table,也可以称为 Segment Table,它是一个数组,数组中的每一项描述了程序中每一个Segment的信息,Segment 的信息需要在运行时刻使用。
- Section Header Table,它也是个数组,数组的每一项描述了每一个Section的信息,Section 的信息需要在程序编译和链接的时候使用。
- Segments,就是各个 Segment。Segment 则记录了每一段数据(包括代码等内容)需要被载入到哪里,记录了用于指导应用程序加载的各类信息。
- Sections,就是各个 Section。Section 记录了程序的代码段、数据段等各个段的内容,主要是链接器在链接的过程中需要使用。
补全 readelf.c
文件
- 方法1
// get section table addr, section header number and section header size.
shdr = (Elf32_Shdr *)(binary + ehdr -> e_shoff); //section table addr
sh_entry_count = ehdr -> e_shnum; //section header number
sh_entry_size = ehdr -> e_shentsize; //section header size
// for each section header, output section number and section addr.
for(Nr = 0; Nr < sh_entry_count; ++ Nr)
{
printf("%d:0x%x\n" , Nr , shdr->sh_addr);
shdr ++ ;
}
- 方法2
// get section table addr, section header number and section header size.
ptr_sh_table = binary + ehdr -> e_shoff;
sh_entry_count = ehdr -> e_shnum; //section header number
sh_entry_size = ehdr -> e_shentsize; //section header size
// for each section header, output section number and section addr.
for(Nr = 0; Nr < sh_entry_count; ++ Nr)
{
shdr = (Elf32_Shdr *)(ptr_sh_table);
printf("%d:0x%x\n" , Nr , shdr->sh_addr);
ptr_sh_table += sh_entry_size;
}
解析 testELF 文件
Exercise 1.3
实验使用的操作系统内核就是我们上面生成的vmlinux文件,这个文件是个可执行文件,ELF格式。它是经过多个.c文件编译成.o文件后,然后链接器将多个.o文件链接成一个最终的可执行文件vmlinux。我们的模拟器gxemul会把vmlinux文件加载到内存,然后跳转到内核入口(其实是个地址),CPU会从这个入口地方开始取址执行,这样操作系统就启动完毕了。内核文件里面有代码,数据等,内核文件应该被加载到哪个地方呢?换而言之这些代码和数据应该存储在内存中哪个地方?通过内存布局图可以发现我们需要把内核文件加载到Kernel Text
处,也即是地址0x80010000
处。如何设置内核加载的位置?
首先我们需要知道,一个可执行文件是根据什么加载到内存。是根据ELF文件里的内容,ELF文件记录了这个文件的各个段应该被加载到哪个地址,然后程序把这个文件加载到这个地址(这个在后面Lab3里面会让你来实现这个过程)。我们可以看到vmlinux文件是由多个.o文件链接而成的,然后得到ELF文件vmlinux。ELF文件记录了这个文件应该被加载到哪个地方,因此我们可以在链接生成ELF文件的时候,指定这个加载位置为0x80010000
!将内核文件加载到指定位置上,CPU不一定从这个位置开始执行。指导书上也说了程序入口的几种设置方法,本实验显然是使用 ENTRY() 指令指定了程序入口为 _start 函数。
补全 tools/scse_03.lds
- 将起始地址设为
0x80010000
SECTIONS
{
. = 0x80010000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
end = . ;
}
查看地址
- 重新
make
,生成vmlinux
内核文件
- 查看各个
section
的地址
Exercise 1.4
补全 boot/start.S
- 栈指针地址应设为
0x80400000
/*To do:
set up stack
you can reference the memory layout in the include/mmu.h
*/
li sp, 0x80400000 // 设置栈指针
jal main // 跳转到main函数
nop
运行 vmlinux
文件
- 重新
make
- 执行命令
gxemul -E testmips -C R3000 -M 64 elf-file
elf-file
为编译生成的vmlinux
文件的路径
Exercise 1.5
补全 lp_Print()
函数
- 找到
%
/* scan for the next '%' */
while((*fmt) != '\0' && (*fmt) != '%') {
OUTPUT(arg, fmt, 1);//其他字符,直接输出
fmt ++ ;
}
/* flush the string found so far */
/* are we hitting the end? */
if((*fmt) == '\0' ) break; //结束了
- 取出参数
/*init the variable */
longFlag = 0;
negFlag = 0;
width = 0;
ladjust = 0;//默认右对齐
prec = 0;
padc = ' ';
/* we found a '%' */
fmt ++ ;
/* check for other prefixes */
/*check for flag */
if(*fmt == '-') ladjust = 1, fmt ++;
else if(*fmt == '0') padc = '0' , fmt ++;
/*check for width */
for(; IsDigit(*fmt); fmt ++ ) width = width * 10 + Ctod(*fmt) ;
/*check for precision */
if(*fmt == '.')
{
fmt ++ ;
for(; IsDigit(*fmt); fmt ++ )
prec = prec * 10 + Ctod(*fmt) ;
}
/* check for long */
if( *fmt == 'l' ) {
longFlag = 1;
fmt ++ ;
}
输出结果
- 重新
make
- 执行命令
gxemul -E testmips -C R3000 -M 64 elf-file
, 查看输出结果
push到远程进行测试
git push
- 结果