从今天开始进入正文,和讲解liteos一样,我们先从连接器脚本开讲。我们知道连接器脚本描述了编译输出程序的布局,那么linux内核编译输出的布局是怎么样的呢?听我慢慢道来,关于连接器脚本的大概使用用途,可以参见
liteos链接器脚本一
liteos链接器脚本二
这里先说明一下对于linux的所有讲解都是基于x86的,像arm他会有自己的连接器脚本,但是大致类似,包括后面分享的内存管理,任务调度等等和硬件相关的是少数,大多还是通用的。
在开始连接器脚本之前,由于后面的连接器脚本部分以及内核解压启动部分的代码分析,会有一些比较重要的知识点,我这里需要提前宣贯普及一下。叫做知识可能不太确切,应该叫做猜测理解。可能做编译器的会更加的明确,不过按照理解不会有逻辑冲突,应该是合理猜测。就是c语言的指针对应的地址是什么地址。
我们知道x86是支持分段和分页的,分段是一定使能的,分页是可以关闭的功能。我们看下图的cpu怎么找到存储
其中绿色部分的分页单元在启动的初期是关闭的,系统可以通过程序使能或者关闭分页单元。分页单元是现代处理器很重要的一个功能模块,它使得物理地址的寻址更加灵活,也增加了系统的安全性
逻辑地址:逻辑地址是通过段寄存器和偏移地址表示的一个地址
比如我们push命令的时候,那么使用的就是堆栈段寄存器和栈指针寄存器表示的一个地址SS:SP,SS是一个16位的段地址,SP是段的偏移地址。
以SS:SP为例,生成的线性地址是addr=(SS<<4)+SP=SS*16+SP,生成的这个addr就是线性地址,也叫做虚拟地址,如果分页单元没有开启(disable掉),那么这个线性地址就是打在地址总线上的物理地址,如果分页单元开启了,那么就需要分页单元进一步映射
物理地址:打在物理地址总线上的地址
就和刚才说的一样,没有开启分页单元,那么线性地址就等于物理地址,如果开启了分页单元,那么物理地址phyaddr=f(addr);我们用函数表示,这个是分页单元的映射功能,实际上从数学角度这就是一个简单的函数。无论是arm还是x86这里分页的功能类似。
我们先解释了x86的寻址方式和逻辑地址,虚拟地址,逻辑地址的意义,然后我们看下面这句c语言,它对应的是什么地址呢?
//向地址0x10000000处写入10000
*(unsigned int*(0x10000000))=10000
看这段代码,大家觉得这个0x10000000这个地址到底是逻辑地址,线性地址还是物理地址呢。
我来说说为我的理解,因为这个在后面kernel的解压布局过程中会用到。首先我们知道linux简化了分段单元,系统启动初期,会将所有的段寄存器的基地址赋值为0。就是上面的SS:SP。这个SS对应的base一直是0。相当于线性地址就是offset,上面的addr=base*8+offset,这个base一直是0,所以addr=offset。
然后我们说这句c语言里面的地址,编译成汇编的时候是什么地址,我认为就是这个offset地址。至于这个地址是物理地址还是虚拟地址,在于是否开启分页单元,如果没有开启分页单元这个0x10000000就等于物理地址,如果开启了那么这个地址就是虚拟地址(线性地址)。
然后我们再说通用的情况,如果段基地址不是0,那么这个是什么呢?还是按照上面的图,这个地址是我们寻址的offset。编译程序的时候,我们会生成代码段,数据段,运行的时候还有堆栈段。所以这个c指针在通用的情况下,实际上也是在段内的偏移地址,我的理解是这样。这样的理解在我们分析后面的连接器脚本,以及启动解压kernel阶段的一些代码是合理的。所以我认为是这样。
我们再提一次,还记得我们在liteos里提到的链接地址吗?是的就是在连接器脚本里面指定的一个地址,如下截图是x86-64架构的linux内核部分使用的连接器脚本的截图,这个.=(0xffffffff80000000 + ALIGN(0x1000000, 0x200000));这个地址就是连接地址,也就是生成的可执行程序的假设的base就是这个连接器脚本开始指定的这个地址,在liteos里面指定0。那么在我们的linux里面最后kernel使用的连接器脚本这里为啥不是0呢?这个就和我们的分页有关系了。
我们接下来的讲解都是基于x86的,所以如果有可能我还是建议大家能有一个学习的开发板,当然如果你自己有不用的老的电脑也可以,不建议初学者在自己日常使用的电脑实验,因为可能经常性的破坏电脑系统,需要重新刷机,所以我建议是有一个不用的硬盘,配合一个不用的电脑,软件不怕整坏了。
请时刻记住我们提到的这几个地址概念,在分析连接器脚本以及kernel启动的时候,理解链接地址,虚拟地址,物理地址的关系很重要。非常重要。欲知后事如何,请看下回分解。