uClinux 移植和分析(转载)
移植和分析
简介:
前一段时间,曾先后移植了 和 的内核,
我的移植基本上是从零做起, 并没有支持该目标机的代码,所以这
个移植工作基本上是新增加对一种目标机的支持。
工作过程中,我学到了不少知识,除了操作系统,还了解了一些编
译,调试,汇编,链接的的技术,在此我会一并介绍,可能介绍比较多
的是连接器,因为这个相对和操作系统联系更加紧密一些。
我希望能够与大家分享自己经验,同时,有错误和不当的地方欢迎
网友指出,共同进步,这是我写这些原创帖的动力。
编程并非零和的游戏。将己所知教给程序员同胞,他们并不会夺你
所知。能将我所知与人分享,我感到高兴,因为我身在其中、热爱编程。”
下用户程序的执行
之所以从用户程序谈起,是因为我们平常接触最多的还是应用程序。
从应用程序引出到操作系统我觉得比较自然。下面就从一个简单例子介
绍一个程序如何在操作系统中运行。
假如有个 程序:
!
"
#$%&'()*%+
&+
,
这是一个最简单不过的程序了,一般一个 语言程序,都从 开始
执行。那么, 函数是不是与其他函数有所区别,地位有些特殊呢?
不是的。 函数和其他函数地位一样。其实,我们完全可以做到让一个
程序从任何地方开始执行。比如 ,它就没有 函数,大家都知道,
系统执行过启动的一段汇编后,就会跳转到位于 - 中的
./&& 中开始执行。
那么为什么用户程序都要从 函数执行呢?这就是用户 库的原因。
一般用户用 语言开发时会调用一些库函数,编译成 01 文件后,在链接过
程中把库函数的二进制代码链接进入程序,最后形成二进制可执行文件。
链接过程中,链接器会在用户程序前插入一些初始化的代码。 下
是在 . 中我移植的是 0 库。不管什么平台下什么形式的 .,
这个文件最后几行代码中肯定有一个 1#或者 或 0 等转移指令
或//0/。这就是为什么你的程序都从 开始执行。如果你把
这个跳转标号改成任意一个标号,比如 $。而你的程序里面既有 ,又
有 $,则这种情况下,程序就先从 $ 开始执行。所以, 函数和其他
函数一样,并没有特殊地位。
下面谈谈在 中, 函数的 是参数怎样传递的。我们
以 2 格式可执行文件为例。 下支持一种叫 2 的可执行文件格式。
这种文件格式比较简单,基本上是平铺的,所以叫 2 很形象。现在好像
内核的版本已经能够支持 &$ 格式的文件执行了。不过为了
举例简单,我还是用 2 格式举例。这里暂不分析 2 文件格式,我们把注
意力放到参数传递上。 开发用户程序,首先当然是编码,然后编译,
编译生成的文件是 &$ 格式的,所以要用工具 &$2 将 &$ 文件转换成 2,
假设这个工作已经完成。
我们在 的 .& 下执行一个文件 $3,$ 是程序名,3 是
参数。学过 语言的都知道,,3 作为参数会传递给 ,其中 =4,
!5%$% !5%% !5%3%。这些参数是如何传递进来的呢。
在你执行一个程序的时候,操作系统会调用
程中把库函数的二进制代码链接进入程序,最后形成二进制可执行文件。
链接过程中,链接器会在用户程序前插入一些初始化的代码。 下
是在 . 中我移植的是 0 库。不管什么平台下什么形式的 .,
这个文件最后几行代码中肯定有一个 1#或者 或 0 等转移指令
或//0/。这就是为什么你的程序都从 开始执行。如果你把
这个跳转标号改成任意一个标号,比如 $。而你的程序里面既有 ,又
有 $,则这种情况下,程序就先从 $ 开始执行。所以, 函数和其他
函数一样,并没有特殊地位。
下面谈谈在 中, 函数的 是参数怎样传递的。我们
以 2 格式可执行文件为例。 下支持一种叫 2 的可执行文件格式。
这种文件格式比较简单,基本上是平铺的,所以叫 2 很形象。现在好像
内核的版本已经能够支持 &$ 格式的文件执行了。不过为了
举例简单,我还是用 2 格式举例。这里暂不分析 2 文件格式,我们把注
意力放到参数传递上。 开发用户程序,首先当然是编码,然后编译,
编译生成的文件是 &$ 格式的,所以要用工具 &$2 将 &$ 文件转换成 2,
假设这个工作已经完成。
我们在 的 .& 下执行一个文件 $3,$ 是程序名,3 是
参数。学过 语言的都知道,,3 作为参数会传递给 ,其中 =4,
!5%$% !5%% !5%3%。这些参数是如何传递进来的呢。
在你执行一个程序的时候,操作系统会调用
(/&&&(6&&&#.#/&.&.,
这个操作会根据文件路径打开文件,装入内存, 就是放到命令行参数,&# 是
环境变量参数。
在装入文件时,系统会根据不同的文件格式调用不同文件装入的 (&,如果
是 2 格式,就会调用 (/2/03,在 $.-0$/2 中。有关参数,会
根据一路传递下来的 &# 首先处理一遍计算出参数的个数 &。然后在函
数 &&/2/0&. 里面建立好参数表。整个函数代码如下:
..&(&&/2/0&..&(##./0#
0#
"
.&(&#+
.&(.#+
4#5##+
50#7+
8&50#7&+
9(3+
:.#5.&(*
.&(.;&$<.&(#+
=.#5&>+
?.#+
.#5>+
5.#+
2/./.#+
4$2/#/&#//."
.#+#/.&.&(&#.#+
8.#+#/.&.&(.#+
9,
:#/.&.#+
=&77/.5.&(#+
?'&7"
#/.&.&(#>>+
("
&/.&(3#+#>>+
4,'&(3+
,
8#/.&.&(@ABB+
9&77/&(5&77&/.5.&(#+
:'&&7"
=#/.&.&(#&#+&#>>+
?("
4&/.&(3#+#>>+
4,'&(3+
4,
44#/.&.&(@ABB&#+
4&77&/&(5.&(#+
48&.&(.#+
,
9行是变量声明。其中 和 & 分别记录前面已经计算出来的参数个数和
环境变量参数个数。#5## 是参数和环境变量数组的指针,.# 是你要执行程序的用户区
堆栈,就是 $ 程序执行时,用户空间堆栈的起始地址。=是一个堆栈调整。首
先 .# 移动 &> 个单位,这 &> 个用来存放一共 & 个 &# !7& &#!
元素
地址的,多余一个放 ,表示 &# 数组结束。然后 .# 在移动 > 各单位,留出 >
单位空间,这 > 个单位是用来存放 个 !7 !元素地址的,多
余一个也放 ,表示 数组结束。经过堆栈调整, 和 &# 各自指向自己在堆栈中
的位置。如果开始堆栈初值记为 /.#,则现在 /.#&>,
5&#>。
无关紧要,略去不提。4:又是一次堆栈调整。是 .# 再移动 个单
位然后将 &# 放入这个地址此时 /.#&>然后8又将 .# 移动一个
单位将 写入:是移动堆栈后将 也写入里面
=48行是将 !7 !在 # 所指向地方依次写入 所指堆栈
区域中然后再将 &# !7&(3#+#>>+
4,'&(3+
4,
44#/.&.&(@ABB&#+
4&77&/&(5.&(#+
48&.&(.#+
,
9行是变量声明。其中 和 & 分别记录前面已经计算出来的参数个数和
环境变量参数个数。#5## 是参数和环境变量数组的指针,.# 是你要执行程序的用户区
堆栈,就是 $ 程序执行时,用户空间堆栈的起始地址。=是一个堆栈调整。首
先 .# 移动 &> 个单位,这 &> 个用来存放一共 & 个 &# !7& &#!
元素
地址的,多余一个放 ,表示 &# 数组结束。然后 .# 在移动 > 各单位,留出 >
单位空间,这 > 个单位是用来存放 个 !7 !元素地址的,多
余一个也放 ,表示 数组结束。经过堆栈调整, 和 &# 各自指向自己在堆栈中
的位置。如果开始堆栈初值记为 /.#,则现在 /.#&>,
5&#>。
无关紧要,略去不提。4:又是一次堆栈调整。是 .# 再移动 个单
位然后将 &# 放入这个地址此时 /.#&>然后8又将 .# 移动一个
单位将 写入:是移动堆栈后将 也写入里面
=48行是将 !7 !在 # 所指向地方依次写入 所指堆栈
区域中然后再将 &# !7&# &!也是由 # 所指写入 &# 所指的堆栈区域中
在写入同时还要设置进程控制块相应的数据结构如 /.&/.&/&( 等
下面举例和画图来说明过程比如执行 $3此时 54 !5%$%
!5%% !5%3%&5&# !5%#5-0%假设用户堆栈起始
空间堆栈地址是 .#5$##5则处理过后在 $ 执行前他的用户空
间堆栈 $& 如下C
$DD
&E$D&# !5=D7指向%#5-0%
&E$=DD
&E$D !59D7指向%3%
&E$D !5D7指向%%
&E&D !5D7指向%$%
&E&=D.(($&E$D
在写入同时还要设置进程控制块相应的数据结构如 /.&/.&/&( 等
下面举例和画图来说明过程比如执行 $3此时 54 !5%$%
!5%% !5%3%&5&# !5%#5-0%假设用户堆栈起始
空间堆栈地址是 .#5$##5则处理过后在 $ 执行前他的用户空
间堆栈 $& 如下C
$DD
&E$D&# !5=D7指向%#5-0%
&E$=DD
&E$D !59D7指向%3%
&E$D !5D7指向%%
&E&D !5D7指向%$%