uClinux移植和分析(1)

fpga在线升级 linux_Linux在Xilinx FPGA上的移植 1 引言嵌入式系统是以应用为中心、以计算机技术为基础, 软硬件可裁, 符合应用系统对功能、可靠性、体积、功耗等严格要求的专用系统。SOPC技术作为一门全新的综合性电子设计技术, 结合了片上系统SOCPLD、FPGA各自的优点, 集成了CPU、DSP、存储器、外围I /O及可编程逻辑, 它涵盖了嵌入式系统设计技术的全部内容, 是目前嵌入式系统设计的中坚力量。随着SOPC技术的发展, FPGA规模的... 阅读详情
2007-12-25 09:25:19

字体变小 字体变大

简介:
前一段时间,曾先后移植了uClinux-2.0.x和uClinux-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(或者call或br等转移指令) 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格式的,所以要用工具elf2flt将elf文件转换成flat,假设这个工作已经完成。

我们在uclinux的shell下执行一个文件foo x y,foo是程序名,x, y是参数。学过C语言的都知道,x,y作为参数会传递给main,其中argc=3,
argv[0]="foo", argv[1]="x", argv[2]="y"。这些参数是如何传递进来的呢。在你执行一个程序的时候,操作系统会调用程中把库函数的二进制代码链接进入程序,最后形成二进制可执行文件。链接过程中,链接器会在用户程序前插入一些初始化的代码。uClinux下是在crt0.s中(我移植的是uClibc库)。不管什么平台下什么形式的crt0.s,这个文件最后几行代码中肯定有一个jmp(或者call或br等转移指令) 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格式的,所以要用工具elf2flt将elf文件转换成flat,假设这个工作已经完成。

我们在uclinux的shell下执行一个文件foo x y,foo是程序名,x, y是参数。学过C语言的都知道,x,y作为参数会传递给main,其中argc=3,
argv[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-
(5) int envc = bprm-
(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- (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- (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- (35) return (unsigned long)sp;
}
(1) -(6)行是变量声明。其中argc和envc分别记录前面已经计算出来的参数个数和环境变量参数个数。p=pp是参数和环境变量数组的指针,sp是你要执行程序的用户区堆栈,就是foo程序执行时,用户空间堆栈的起始地址。(8)-(11)是一个堆栈调整。首先sp移动envc+1个单位,这envc+1个用来存放一共envc个envp[0]-

(12)无关紧要,略去不提。(13)-(17)又是一次堆栈调整。(14)是sp再移动1个单位,然后将envp放入这个地址(此时envp=init_sp-(envc+1)),然后(15)又将sp移动一个单位,将argv写入. (17)是移动堆栈后将argc也写入里面.

(18)-(35)行是将argv[0]- (31) } while (dummy);
(32) }
(33) put_user((unsigned long) NULL, envp);
(34) current- (35) return (unsigned long)sp;
}
(1)-(6)行是变量声明。其中argc和envc分别记录前面已经计算出来的参数个数和环境变量参数个数。p=pp是参数和环境变量数组的指针,sp是你要执行程序的用户区堆栈,就是foo程序执行时,用户空间堆栈的起始地址。(8)-(11)是一个堆栈调整。首先sp移动envc+1个单位,这envc+1个用来存放一共envc个envp[0]-
argv=envp-(argc+1)。

(12)无关紧要,略去不提。(13)-(17)又是一次堆栈调整。(14)是sp再移动1个单位,然后将envp放入这个地址(此时envp=init_sp-(envc+1)),然后(15)又将sp移动一个单位,将argv写入. (17)是移动堆栈后将argc也写入里面.

(18)-(35)行是将argv[0]-

下面举例和画图来说明过程.比如执行foo x y,此时argc=3,argv[0]="foo",argv[1]="x", argv[2]="y", envc=1, envp[0]="path=/bin". 假设用户堆栈起始
空间堆栈地址是sp=0x1f0000,pp=0x1c0000.则处理过后在foo执行前,他的用户空间堆栈frame如下:


--------------------------------
0x1f0000 | 0000 |
--------------------------------
0x1efffc | envp[0] = 0x1c0008 | ---- <指向"path= bin"
--------------------------------
0x1efff8 | 0000 |
--------------------------------
0x1efff4 | argv[2] = 0x1c0006 | ----- <指向"y">
--------------------------------
0x1efff0 | argv[1] = 0x1c0004 | ----- <指向"x">
--------------------------------
0x1effec | argv[0] = 0x1c0000 | ----- <指向"foo">
--------------------------------
0x1effe8 | start addr of envp = 0x1efffc|
--------------------------------

到r2-r6里来传递。当然,如果超过5个,就要借助堆栈了。

既然main带了参数,那么在调用main之前,要把argc放到r2里面,argv放到r3里面,envp放到r4里面。刚才说了,sp是用户空间堆栈起始地址。所以在开始执行foo代码时候,r0=sp,在上文例子里r0等于0x1effe0.则如下伪汇编代码可以让参数装入正确寄存器。

load r2, (r0) /* r2 = argc */
load r3, (r0, 4) /* r3 = argv */
load r4, (r0, 8) /* r4 = envp */
call main /* 跳转到main函数 */

call _exit

以上代码就是最简单的进入main函数前的预处理。当然,不同系统不同格式文件处理方式是不同的,刚才的一些例子是我碰到的一些情景和解决方案。

我这个程序例子还没有完全讲完,比如后面printf怎么处理等,不过手都酸了,就先讲到main函数的参数传递吧。刚学c语言那阵觉得main挺神秘,做过系统就知道,其实main跟别的函数没有任何区别:)

写了半天,头晕脑胀,肯定还有一些错误或者不够优化的地方,欢迎拍砖。没讲清楚

搭建STM3210E-EVAL移植uClinux环境二 再运行即可,如果最后发现里面没有STM 选项,再把uclinux_on_stm32补丁安装上。首先第一个错误是228行245行语法已经弃用,我们来看一下Makefile文件中的内容。看到上面两个错误已经搞定了,下面报错没有找到CC命令,那就安装gcc编译环境。接着上一篇,安装完make 以后,继续输入make menuconfig。发现还有最后一个错误,成功就在眼前,继续来解决报错信息。一看发现报了很多错误,下面一个一个来解决。安装完以后可以输入gcc -v查看版本信息。在scripts/config/ 阅读详情

相关推荐

搭建STM3210E-EVAL移植uClinux环境

第三个文件是两个补丁uClinux-dist-20080808-20090112.patch.gzuClinux_on_stm32.patch.gz,后面这个补丁是针对STM32 MCU进行的,进入uClinux_dist文件夹输入zcat /路径/uClinux-dist-20080808-20090112.patch.gz | patch -p1。这里用户名密码,要记住,后期在进入root模式一些应用上会用到,随后等待安装,此步骤时间较长,安装完以后重启虚拟机。选择中文,也可根据自己的喜好。

victoryWDD的博客 661

uclinux移植分析

前一段时间,曾先后移植uClinux-2.0.xuClinux-2.4.x的内核, 我的移植基本上是从零做起,linux并没有支持该目标机的代码,所以这 个移植工作基本上是新增加对一种目标机的支持。 工作过程中,我学到了不少知识,除了操作系统,还了解了一些编 译,调试,汇编,链接的的技术,在此我会一并介绍,可能介绍比较多 的是连接器,因为这个相对操作系统联系更加紧密一些。 我希望能够与大家分享自己经验,同时,有错误不当的地方欢迎 网友指出,共同进步,这是我写这些原创帖的动力。

【Java实战系列】「技术盲区」Double与Float的坑与解决办法以及BigDecimal的取而代之

探究背景 涉及诸如float或者double这两种浮点型数据的处理时,偶尔总会有一些怪怪的现象,不知道大家注意过没,举几个常见的栗子: 条件判断超预期 System.out.println( 1f == 0.9999999f ); // 打印:false System.out.println( 1f == 0.99999999f ); // 打印:true 数据转换超预期 float f = 1.1f; double d = (double) f; System.out.println(

zybb166的博客 468

终于将uClinux移植fpga成功

刚刚上传图片失败,重来 历时两个星期,终于将uClinux移植于nios ii成功;激动的心情难以掩饰,高兴的同时,我希望记录下我的移植坎坷之旅:    首先我是按照真 OO无双的博客一步一步走下去的: file:///C:/Users/Frank/Desktop/(%E5%8E%9F%E5%89%B5)%20%E5%A6%82%E4%BD%95%E5%9C%A8DE2%E4%B8

frank_wff的专栏 2156

main函数

  那么为什么用户程序都要从main函数执行呢?这就是用户C库的原因。一般用户用c语言开发时会调用一些库函数,编译成obj文件后,在链接过程中把库函数的二进制代码链接进入程序,最后形成二进制可执行文件。链接过程中,链接器会在用户程序前插入一些初始化的代码。uClinux下是在crt0.s中(移植的是uClibc库)。不管什么平台下什么形式的crt0.s,这个文件最后几行代码中肯定有一个jmp(

huangtingyu24的专栏 401

Java中Double的高精度问题及bigdecimal解决方式

Java中Double的高精度问题及bigdecimal解决方式 最近有空写了点老的J2EE的代码,发现有一个十分有意思的问题,当用Hibernate从数据库里把浮点数读取出来的时候做一些比如累加的工作,例如 summary 或者递减之类的,就会发现在最后的结果中会出现些许问题。 如:3.41+5.2+56.2+23.3+... (这类两位小数的价钱),结果会出现103.000000000...

Jaylon Wang的专栏 979

Java BigDecimal 解决浮点精度问题

Java 浮点类型 float double 由于二进制存储方式,无法精确表示部分十进制小数,导致计算时出现精度问题。BigDecimal 类通过整数标度方式精确存储十进制数,是解决该问题的标准方案。创建时应避免使用 BigDecimal(double) 构造方法,推荐使用字符串构造或 valueOf() 方法。BigDecimal 提供完整的数学运算方法,除法运算必须指定舍入模式(如金融计算常用 HALF_UP 四舍五入)。使用时需注意其不可变性精确计算特性,适用于金融、科学等需要高精度计算的场景

qq_29515513的博客 1106

java double相加_Java 学习笔记---Java double类型相加问题

多个double类型的数直接相加的时候,可能存在精度误差.( 由于计算机算法以及硬件环境决定只能识别 0 1。计算机默认的计算结果在都在一个指定精度范围之内,想往深的了解,可以学习数值分析) 在金融方面是绝对不允许的,好在java开发者有这个先见之明。 java.math.*里面提供了BigDecimal类(提供高精度计算的方法)这个时候就要采用BigDecimal函数进行运算第一步、建立Str...

weixin_36180471的博客 734

浮点型变量(floatdouble)BigDecimal的使用

1、浮点型变量(floatdouble) 带小数的变量在Java中称为浮点型,Java的浮点型有两种:floatdouble。 float类型代表单精度浮点数,占4个字节、32位。double类型代表双精度浮点数,占8个字节、64位。 Java语言的浮点数有两种表示形式: 1)十进制数形式:例如3.14、314.0、0.314。浮点数必须包含一个小数点,否则会被当成int类型处理。 ...

weixin_41797098的博客 1万+

Java中解决doublefloat计算精度不准的问题

1. 问题背景 我们知道浮点数是无法在计算机中准确表示的,例如0.1在计算机中只是表示成了一个近似值,因此,浮点数的运算结果具有不可预知性。 在进行数字运算时,如果有double或float类型的浮点数参与计算,偶尔会出现计算不准确的情况。 public class BigDeciTest { public static void main(String[] args){ ...

qq_35131374的博客 896

BigDecimal 的那些坑事儿

背景:最近做金融的项目,对于金额数字特别敏感,但是经常精度缺失,找的文章去学习。 最近查看rebate数据时,发现一个bug,主要现象是,当扣款支付宝的账号款项时,返回的是数字的金额为元,而数据库把金额存储为分,这中间要做元与分的转化,这个转化规则很简单,就是*100的,所以一开始代码很简单,如下。 [java] view plain copy Floa

orangeTop的博客 566

BigDecimal

/* A:BigDecimal的概述             * 由于在运算的时候,float类型double很容易丢失精度,演示案例。             * 所以,为了能精确的表示、计算浮点数,Java提供了BigDecimal             * 不可变的、任意精度的有符号十进制数。         * B:构造方法             * public Bi

wanghonhjunking的博客 386

第二章、3.java高精度数字

java高精度数字     BigInteger      高精度整数     BigDecimal    高精度小数 这个两个类都在java.math包下。 由于int ,long长度有限,float,double运算有误差,所以使用BigInteger与BigDecimal进行高精度运算。 需要使用对象的方法add(),substract(),muliply(),divide()进行

567

数据精度及其选型

为什么关注数字精度?   在大部分场景,我们默认整数或者保留两位小数位,分别对应IntDouble,而没有进一步去了解其精度,因为大部分应用,这样的精度数据类型是足够应付的,但是在某些科学计数及特殊的商业范畴,可能需要更高精度的数字表达,这就要进一步了解数字的精度。大部分语言都提供两种基本精度类型,一种是float、一种是double ;实际上如果小数位如果是固定的,floatdoub...

truemylife 1093

Java浮点数运算的精确度四舍五入的问题

浮点运算有时是不精确的,只要是超过精度能表示的范围就会产生误差。往往产生误差不是因为数的大小,而是因为数的精度,产生的结果接近但不等于想要的结果,所以在使用 float double 作精确运算的时候往往采用一些方案来实现运算的准确。

happylifex的专栏 1630

如何解决C语言浮点数精度丢失的问题?

在精度要求比较高或需要进行复杂计算时,建议使用double类型。在进行精度要求极高的计算时,可以使用Java提供的BigDecimal类型进行计算,它能够保证精度不会损失。1. 由于计算机中浮点数表示的方式为近似值,所以在进行浮点数的比较时,需要使用特定的比较方式,避免误判。在进行大量的浮点数运算时,为了提高运算的速度准确性,可以考虑将小数转换为整数进行计算,再将结果转换回小数。2. 在进行除法运算时,需要判断被除数是否为0,避免抛出异常。10000次的时候,误差已经到了100。1000次的时候还好。

pcjiushizhu的专栏 8333

金钱类型BigDecimal

在项目中你们设计金钱用的是什么类型呢?是double还是float类型呢!其实这两个都不行,给你们举个例子: System.out.println(0.05+0.01); System.out.println(1.0-0.42); System.out.println(4.015*100); System.out.println(123.3/100); 这是一个很简单的加减乘除,我们口算都能算出来的数可他出现的结果居然是 当然并不是每数字都是这样,这只是一个例子,为什么会造成这种现象呢,

tianxiu11的博客 385
上一篇: 嵌入式Linux系统的设计与应用
下一篇: uClinux移植和分析(2)
mybirdsky
博客等级 码龄19年 415粉丝 2035原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值