目录
一、解读Lab5的kernel.ld文件
1.获取bin/kernel的symbols table
2.excel表格数据整理
3.将kernel_symbol_table_1按照B列排序
4.kernel.ld文件内容
5.SECTIONS->. = 0xC0100000;解读
6.ENTRY(kern_entry)解读
7.SECTIONS->.text解读
8.SECTIONS->PROVIDE(etext = .);解读
9.SECTIONS->.rodata解读
10.SECTIONS->.stab解读
11.SECTIONS->.data解读
12.SECTIONS->.data.pgdir解读
13.SECTIONS->.bss解读
二、总结
对于用户进程的内存布局,是由编译和连接过程决定。我认为编译和链接会确定可执行文件的虚拟内存布局,这种结论可以通过解读可执行文件的符号表证实。
一、解读Lab5的kernel.ld文件
为了解读kernel.ld文件,需要通过这个文件编译出可执行文件bin/kernel,然后需要获得kernel symbols table,最后解读kernel symbols table中的地址字段,就能够理解清楚bin/kernel的内存布局。
1.获取bin/kernel的symbols table
通过命令readelf -s bin/kernel获取kernel的symbols table
2.excel表格数据整理
将步骤1的kernel的symbols table拷贝到excell表格中,获得原始的数据kernel_symbol_table_1。
3.将kernel_symbol_table_1按照B列排序
排序后的kernel_symbol_table_2,他的虚拟内存地址从小到大排列。
4.kernel.ld文件内容
/* Simple linker script for the ucore kernel.
See the GNU ld 'info' manual ("info ld") to learn the syntax. */
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(kern_entry)
SECTIONS {
/* Load the kernel at this address: "." means the current address */
. = 0xC0100000;
.text : {
*(.text .stub .text.* .gnu.linkonce.t.*)
}
PROVIDE(etext = .); /* Define the 'etext' symbol to this value */
.rodata : {
*(.rodata .rodata.* .gnu.linkonce.r.*)
}
/* Include debugging information in kernel memory */
.stab : {
PROVIDE(__STAB_BEGIN__ = .);
*(.stab);
PROVIDE(__STAB_END__ = .);
BYTE(0) /* Force the linker to allocate space
for this section */
}
.stabstr : {
PROVIDE(__STABSTR_BEGIN__ = .);
*(.stabstr);
PROVIDE(__STABSTR_END__ = .);
BYTE(0) /* Force the linker to allocate space
for this section */
}
/* Adjust the address for the data segment to the next page */
. = ALIGN(0x1000);
/* The data segment */
.data : {
*(.data)
}
. = ALIGN(0x1000);
.data.pgdir : {
*(.data.pgdir)
}
PROVIDE(edata = .);
.bss : {
*(.bss)
}
PROVIDE(end = .);
/DISCARD/ : {
*(.eh_frame .note.GNU-stack)
}
}
5.SECTIONS->. = 0xC0100000;解读
1)在Lab5的实验环境中,编译获取编译过程输出
2)链接器将bin/kernel和obj/__user_XXX.out应用程序链接在一起
+ ld bin/kernel ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/entry.o obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/ide.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o obj/kern/trap/trapentry.o obj/kern/mm/pmm.o obj/kern/mm/swap_fifo.o obj/kern/mm/vmm.o obj/kern/mm/kmalloc.o obj/kern/mm/swap.o obj/kern/mm/default_pmm.o obj/kern/fs/swapfs.o obj/kern/process/switch.o obj/kern/process/entry.o obj/kern/process/proc.o obj/kern/schedule/sched.o obj/kern/syscall/syscall.o obj/libs/string.o obj/libs/printfmt.o obj/libs/hash.o obj/libs/rand.o -b binary obj/__user_hello.out obj/__user_badarg.out obj/__user_forktree.out obj/__user_badsegment.out obj/__user_faultread.out obj/__user_pgdir.out obj/__user_exit.out obj/__user_softint.out obj/__user_waitkill.out obj/__user_spin.out obj/__user_yield.out obj/__user_divzero.out obj/__user_testbss.out obj/__user_faultreadkernel.out obj/__user_forktest.out |
由于obj/kern/init/entry.o是第一个链接的文件,所以他的地址使用起始地址0xC0100000,不难发现symbol table中的地址顺序是依靠实际链接目标文件顺序
6.ENTRY(kern_entry)解读
bin/kernel的入口地址是kern_entry,加载器从该地址开始加载并执行程序。
7.SECTIONS->.text解读
该段存放的是代码段,代码段表示执行程序集合,具体在符号表中的体现就是函数地址。
8.SECTIONS->PROVIDE(etext = .);解读
该部分表示在symbols table中生成一个etext字段。
9.SECTIONS->.rodata解读
.rodata表示read only data segment,下图可以看出在bin/kernel中存在若干个只读数据在rodata数据段中。
其中default_pmm_manager是rodata,主要是用const修饰的全局变量
10.SECTIONS->.stab解读
bin/kernel的调试信息存在在stab段中
该段内存虚拟地址如下:
11.SECTIONS->.data解读
.data是数据段,bin/kernel的数据段如下
其中,bootstack是数据段,在汇编语言中进行定义
其中,shiftmap是数据段,在c语言中进行定义,shiftmap变量是全局变量,并且已经初始化了。
其中,idt_pd是数据段,在c语言中进行定义,idt_pd变量是全局变量,并且已经初始化了。
总结来看,.data表示已经初始化,非零值的数据段
12.SECTIONS->.data.pgdir解读
.data.pgdir代表数据段中的页表段,该段单独存在,表示页表的存在范围。在entry.S汇编代码中,对页表段进行了创建赋值。
在symbol table中,表示有两个函数处理页表
13.SECTIONS->.bss解读
.bss是数据段,bin/kernel的数据段如下
其中buf是bss中的成员,它位于readline.c函数中。
其中is_panic是bss中的成员,位于panic.c函数中
其中swap_out_num是bss中的成员,位于swap.c函数中
.bss段存放的是未初始化的全局变量,但是当全局数据初始化为0的时候,也将该变量放在.bss中处理。
二、总结
关于用户进行内存布局,最重要的是代码段、数据段、调试段的符号地址。这些地址在链接器的控制下,给定symbols table。其中ENTRY(kern_entry)规定加载器的加载地址,其中. = 0xC0100000;规定bin/kernel的内存布局起始地址,其中symbol table中的地址顺序是依靠实际链接目标文件顺序。
在bin/kernel中有三类数据段,分别是.rodata、.data、.bss。最让人分不清的是.data和.bss的区别。在这里,.data表示已经初始化的全局变量区域(非0初始化),而.bss表示未初始化化的全局变量区域(包括初始化为0)。对于大多数的操作系统,在程序加载的时候会把bss全局变量清零,无需动手清零,但是动手清零是个好的编程习惯。