求索
路漫漫其修远兮,吾将上下而求索
 
2008-06-23 20:35:46 | uClinux移植和分析ZT
 

个人觉得写得不错,适合作为uclinux移植的入门教程 

 

uClinux移植和分析(1) 简介:

前一段时间,曾先后移植了uClinux-2.0.xuClinux-2.4.x的内核,我的移植基本上是从零做起,linux并没有支持该目标机的代码,所以这个移植工作基本上是新增加对一种目标机的支持。

工作过程中,我学到了不少知识,除了操作系统,还了解了一些编译,调试,汇编,链接的的技术,在此我会一并介绍,可能介绍比较多的是连接器,因为这个相对和操作系统联系更加紧密一些。

我希望能够与大家分享自己经验,同时,有错误和不当的地方欢迎网友指出,共同进步,这是我写这些原创帖的动力。

编程并非零和的游戏。将己所知教给程序员同胞,他们并不会夺你所知。能将我所知与人分享,我感到高兴,因为我身在其中、热爱编程。

——John Carmack

uClinux下用户程序的执行

之所以从用户程序谈起,是因为我们平常接触最多的还是应用程序。从应用程序引出到操作系统我觉得比较自然。下面就从一个简单例子介绍一个程序如何在操作系统中运行。

假如有个c程序:

int main(int argc, char **argv[])
{
  printf("hello world!\n");
  return 0;
}

这是一个最简单不过的程序了,一般一个C语言程序,都从main开始执行。那么,main函数是不是与其他函数有所区别,地位有些特殊呢?不是的。main函数和其他函数地位一样。其实,我们完全可以做到让一个c程序从任何地方开始执行。比如linux,它就没有main函数,大家都知道,系统执行过启动的一段汇编后,就会跳转到位于init/main.c中的start_kernel中开始执行。

那么为什么用户程序都要从main函数执行呢?这就是用户C库的原因。一般用户用c语言开发时会调用一些库函数,编译成obj文件后,在链接过程中把库函数的二进制代码链接进入程序,最后形成二进制可执行文件。链接过程中,链接器会在用户程序前插入一些初始化的代码。uClinux下是在crt0.s(我移植的是uClibc)。不管什么平台下什么形式的crt0.s,这个文件最后几行代码中肯定有一个jmp(或者callbr等转移指令) main(__uClibc_main)。这就是为什么你的程序都从main开始执行。如果你把这个跳转标号改成任意一个标号,比如foo。而你的程序里面既有main,又有foo,则这种情况下,程序就先从foo开始执行。所以,main函数和其他函数一样,并没有特殊地位。

下面谈谈在uClinux中,main函数的argc,argv是参数怎样传递的。我们以flat格式可执行文件为例。uClinux下支持一种叫flat的可执行文件格式。这种文件格式比较简单,基本上是平铺的,所以叫flat很形象。现在好像uClinux-2.4.x内核的版本已经能够支持elf格式的文件执行了。不过为了举例简单,我还是用flat格式举例。这里暂不分析flat文件格式,我们把注意力放到参数传递上。uClinux开发用户程序,首先当然是编码,然后编译,编译生成的文件是elf格式的,所以要用工具elf2fltelf文件转换成flat,假设这个工作已经完成。

我们在uclinuxshell下执行一个文件foo x yfoo是程序名,x, y是参数。学过C语言的都知道,xy作为参数会传递给main,其中argc3argv[0]="foo", argv[1]="x", argv[2]="y"。这些参数是如何传递进来的呢?在你执行一个程序的时候,操作系统会调用do_execve(char *filename, char**argv, char**envp, struct pt_regs *regs),这个操作会根据文件路径打开文件,装入内存,argv就是放到命令行参数,envp是环境变量参数。

在装入文件时,系统会根据不同的文件格式调用不同文件装入的handler,如果是flat格式,就会调用load_flat_binary(),在fs/binfmt_flat.c中。有关参数,会根据一路传递下来的argv,envp首先处理一遍计算出参数的个数argc,envc。然后在函数create_flat_tables里面建立好参数表。整个函数代码如下:

static unsigned long create_flat_tables(unsigned long pp, struct linux_binprm * bprm)
{
(1) unsigned long *argv,*envp;
(2) unsigned long * sp;
(3) char * p = (char*)pp;
(4) int argc = bprm->argc;
(5) int envc = bprm->envc;
(6) char dummy;
(7) sp = (unsigned long *)\((-(unsigned long)sizeof(char *))&(unsigned long) p);
(8) sp -= envc+1;
(9) envp = sp;
(10) sp -= argc+1;
(11) argv = sp;
(12) flat_stack_align(sp);
(13) if (flat_argvp_envp_on_stack()) {
(14)     --sp; put_user((unsigned long) envp, sp);
(15)     --sp; put_user((unsigned long) argv, sp);
(16) }
(17) put_user(argc,--sp);
(18) current->mm->arg_start = (unsigned long) p;
(19) while (argc-->0) {
(20)     put_user((unsigned long) p, argv++);
(21)     do {
(22)         get_user(dummy, p); p++;
(23)     } while (dummy);
(24) }
(25) put_user((unsigned long) NULL, argv);
(26) current->mm->arg_end = current->mm->env_start = (unsigned long) p;
(27) while (envc-->0) {
(28)     put_user((unsigned long)p, envp); envp++;
(29)     do {
(30)         get_user(dummy, p); p++;
(31)     } while (dummy);
(32) }
(33) put_user((unsigned long) NULL, envp);
(34) current->mm->env_end = (unsigned long) p;
(35) return (unsigned long)sp;
}

(1)-(6)行是变量声明。其中argcenvc分别记录前面已经计算出来的参数个数和环境变量参数个数。p=pp是参数和环境变量数组的指针,sp是你要执行程序的用户区堆栈,就是foo程序执行时,用户空间堆栈的起始地址。(8)-(11)是一个堆栈调整。首先sp移动envc+1个单位,这envc+1个用来存放一共envcenvp[0]->envc[envp-1]元素地址的,多余一个放0,表示envp数组结束。然后sp在移动argc+1各单位,留出argc+1单位空间,这

评论 (2) | 阅读(1025)  | 
以下网友评论只代表其个人观点,不代表本网站的观点或立场

数据加载中......
您还未登录,不能发表评论。或者您可以 登录 后发表。