读Kernel感悟-Linux内核启动-链接脚本

文章来源:http://www.top-e.org/jiaoshi/class/

一般来说,用户是不需要关心section的具体位置的。在用户态,内核会解析elf可执行文件的各个section,然后把它映射到虚拟地址空间。然而,在内核启动时,一切得从零开始。很多在用户态下应用程序不需要操心的东西,例如映射section的任务不得不由内核自己来完成。上一篇感悟揭示了内核如何建立页表,并且把自身的一部分映射到虚拟地址。内核还要负责对BSS段(所有在代码中未定义的全局变量)的初始化(设置为0),这就要求内核知道section的具体位置(否则如何知道该映射哪一部分呢?)

此外,在开启页面映射的过程中,我最为疑惑的是几个常量(页目录swapper_pg_dir,页表pg0等等)是如何确定的。扩展一下。gcc链接可执行文件时,是如何确定变量的地址的?按理说应该有某种途径(命令行参数或者文件)告诉链接器ld如何定位这些变量。最普通如hello world。为什么_start的地址是0x80482e0?于是想到,我们需要一个文件来指定各个section的虚拟地址。在内核源代码里,还看到这个文件arch/i386/kernel/vmlinux.lds.S。不像是普通的汇编文件。原来这就是linker scripts链接器脚本。

在链接器脚本中,.表示当前location counter地址计数器的值。默认为0。

017   . = __KERNEL_START;

表示地址计数器从__KERNEL_START(0xc00100000)开始。

.text:{...}

表示.text section包含了哪几个section

031   . = ALIGN(16);

则表示对齐方式。

具体格式可以调用info ld查看Linker Scripts一节。

链接器脚本指定了各个section的起始位置和结束位置。它还允许程序员在脚本中对变量进行赋值。这使内核可以通过__initcall_start和__initcall_end之类的变量获得段的起始地址和结束地址,从而对某些段进行操作。

根据链接器脚本,以及nm vmlinux的结果,内核中各个section的虚拟地址就很清楚了。以我的机子为例(粗略):

地址分配

text section

从_text:c0100000 A _text

到_etext:c0436573 A _etext

Exception table

从__start___ex_table:c0436580 A __start___ex_table

到__stop___ex_table:c04370b8 A __stop___ex_table

RODATA read only section

.data writable section

.data_nosave section

从__nosave_begin:c050f000 A __nosave_begin

到__nosave_end:c050f000 A __nosave_end

.data.page_aligned section

.data.cacheline_aligned section

.data.read_mostly section

.data.init_task section

init section

从__init_begin c0514000 A __init_begin

到__init_end c0540000 A __init_end

其中.initcall.init section:

从__initcall_start:c053b570 A __initcall_start

到__initcall_end:c053b8c0 A __initcall_end

BSS section

从__bss_start c0540000 A __bss_start

到__bss_end c0594c78 A __bss_stop

其中 swapper进程的页表

从c0540000 B swapper_pg_dir

到c0541000

共一页

empty_zero_page

从c0541000 B empty_zero_page

到c0542000

共一页

pg0 页目录0

从c0595000 A pg0

到init_pg_tables_end

.exitcall.exit

 section

stab section

几个比较重要的section:

bss section,存放在代码里未初始化的全局变量,最后初始化为0。

init sections,所有只在初始化时调用的函数和变量,包括所有在内核启动时调用的函数,以及内核模块初始化时调用的函数。其中最特别的是.initcall.init section。通过__initcall_start和__initcall_end,内核可以调用里面所有的函数。这些section在使用一次后就可以释放,从而节省内存。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值