linux内核初学习笔记(1)

来源:互联网 发布:淘宝上怎么改差评 编辑:程序博客网 时间:2024/05/17 08:30

 

最近开始学习linux内核,之前一年多,陆陆续续学了些嵌入式和arm,linux方面的知识,感觉还是非常的不系统。看着那些书,往往能把书中的例程都一步步做下来,但是学习嵌入式操作系统与学习单片机是不一样的,不是说把例程做下来就什么都懂了,相反,每次完成一个例程,我都感觉更加迷糊了,因为每个例程的背后往往隐含着操作系统内部的一些操作,这些操作不是说把例程的几句代码搞清楚就能搞懂的,而不搞懂这些内涵的东西,我又感觉根本学不到其核心的东西,所以,我决定搞清楚linux内核,学习linux内部的运行机制,这样,对嵌入式linux的学习应该有很大的帮助。

学习linux内核也不是很容易的,而是需要相当的门槛。首先要搞懂Makefile,这个东西先要懂个大概makefile的语法规则,太细节的东西暂时忽略,等到阅读到内核中makefile中有不懂的地方再去查看GUN_make手册就OK了。

接着需要再懂点而程序链接,存储方面的知识,这些我正在研究中。现在正在研究的是lds链接脚本,下面主要说说我在lds学习的一些心得。

稍微玩过一些嵌入式linux的移植,应该知道交叉编译linux时,要用到gcc命令:arm-linux-gcc,这个命令与arm-elf-gcc是等价的。在链接阶段,可以将多个目标文件链接成为一个目标文件,如 arm-elf-ld -Ttext 0x00000000 -g led_On.o -o led_on_elf 这里的-Ttext参数是指定程序存储的地方,但是如果需要设定的内容比较多的话,这样很不方便,于是就用链接脚本,在编译命令行,直接用-T命令行选项来指定一个linker script。

链接脚本中最重要的就是SECTIONS命令:The SECTIONS command tells the linker how to map input sections into output sections,and how to place the output sections in memory.

还有地址计数器‘.’:该符号只能用于SECTIONS 命令内部,初始值为‘0’,可以对该符号进行赋值,也可以使用该符号进行计算或赋值给其他符号。它会自动根据SECTIONS 命令内部所描述的输出段的大小来计算当前的地址。

在SECTIONS 命令中可以作输出段描述,描述的格式如下:

.start ALIGN(4) : {
*(.text.start)
}

.start 为output section name,ALIGN(4)返回一个基于location counter(.)的4 字节对齐的地址值。*(.text.start)是输入段描述,*为通配符,意思是把所有被链接的object 文件中
的.text.start 段都链接进这个名为.start 的输出段。

GNU 官方网站上对.lds 文件形式的完整描述:

SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}

secname 和contents 是必须的,其他的都是可选的。下面挑几个常用的看看:
1、secname:段名
2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代
码段、数据段等)
3、start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。
GNU 网站上说start 可以用任意一种描述地址的符号来描述。
4、AT(ldadr):定义本段存储(加载)的地址。

再看一个例子:

/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
以上,head.o 放在0x00000000 地址开始处,init.o 放在head.o 后面,他们的运行地址
也是0x00000000,即连接和存储地址相同(没有AT 指定);main.o 放在4096(0x1000,是AT
指定的,存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0x1000(加载
处)复制到0x30000000(运行处),此过程也就用到了读取Nand flash。
这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds 连接脚本
文件中分别指定。

最后,再放一个实际的链接脚本看看:

OUTPUT_FORMAT("elf32­littlearm", "elf32­littlearm", "elf32­littlearm")
;指定输出可执行文件是elf 格式,32 位ARM 指令,小端
OUTPUT_ARCH(arm)
;指定输出可执行文件的平台为ARM
ENTRY(_start)
;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
. = 0x00000000 ; 从0x0 位置开始
. = ALIGN(4) ; 代码以4 字节对齐
.text : ;指定代码段
{
cpu/arm920t/start.o (.text) ; 代码的第一个代码部分
*(.text) ;其它代码部分
}
. = ALIGN(4)
.rodata : { *(.rodata) } ;指定只读数据段
. = ALIGN(4);
.data : { *(.data) } ;指定读/写数据段
. = ALIGN(4);
.got : { *(.got) } ;指定got 段, got 段式是uboot 自定义的一个段, 非标
准段
__u_boot_cmd_start = . ;把__u_boot_cmd_start 赋值为当前位置, 即起始位

.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd 段, uboot 把所有的uboot
命令放在该段.
__u_boot_cmd_end = .;把__u_boot_cmd_end 赋值为当前位置,即结束位置
. = ALIGN(4);
__bss_start = .; 把__bss_start 赋值为当前位置,即bss 段的开始位置
.bss : { *(.bss) }; 指定bss 段
_end = .; 把_end 赋值为当前位置,即bss 段的结束位置

原创粉丝点击