我的时光,停在了你的角落…~
Posts tagged 嵌入式
16道嵌入式C语言面试题
Apr 22nd
|
1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL 3. 预处理器标识#error的目的是什么? 如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种 死循环(Infinite loops) 4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢? 这个问题用几个解决方案。我首选的方案是: 数据声明(Data declarations) 5. 用变量a给出下面的定义 答案是: 6. 关键字static的作用是什么? 这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用: 7.关键字const是什么含意? const int a; 前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由: Volatile 8. 关键字volatile有什么含意 并给出三个不同的例子。 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子: 位操作(Bit manipulation) 9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。 对这个问题有三种基本的反应 访问固定的内存位置(Accessing fixed memory locations) 10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。 这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下: 一个较晦涩的方法是: 即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。 中断(Interrupts) 11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。 __interrupt double compute_area (double radius) 这个函数有太多的错误了,以至让人不知从何说起了: 代码例子(Code examples) void foo(void) 这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是“>6”。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。 13. 评价下面的代码片断: unsigned int zero = 0; 对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下: unsigned int compzero = ~0; 这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。 动态内存分配(Dynamic memory allocation) 14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么? 这 里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:下面的代码片段的输出是什么,为什么? char *ptr; 这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是“Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。 Typedef 15. Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子: 以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么? 第一个扩展为 上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。 晦涩的语法 16. C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么? 这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成: from:http://blog.21ic.com/user1/4692/archives/2010/68407.html |
从零构建一个400K的嵌入式Linux根文件系统UCFS
Mar 30th
(本文转自:http://hi.baidu.com/neu_stroller/blog/item/0d09a1448fd18f3887947308.html)
从零构建一个400K的嵌入式Linux根文件系统UCFS
2008年12月11日 星期四 01:39
看过很多文章,都没有明白怎样制作一个文件系统,在网上发现有这么一篇好文章,这是幸运!
转载之,希望让更多的人受益。
(当然我在实践的过程中,把有些不对的地方给改了过来,忘原作者不要介意)
++++++++++++++++++++++++++++++++++++++++
作者:惆怅的桶(tongmvp)
日期:2008-2-22
版权声明:转载请注明出处
++++++++++++++++++++++++++++++++++++++++
从零构建一个400K的嵌入式Linux根文件系统UCFS
所谓根文件系统,即可以作启动盘的文件系统。根文件系统(以下在不引起歧义的地方简称文件系统)主要包括etc/、bin/、sbin/、lib/、proc/等五个根目录。创建根文件系统使用了busybox工具,同时,为了保持交叉编译器和文件系统库的一致性,以buildroot、uClibc和gcc等工具构建了一个交叉编译器。文件系统使用buildroot工具编译的uClibc库。使用该方法创建的文件系统压缩后可以达到400K左右。
1.1 利用buildroot制作交叉编译器
1.1.1 buildroot简介
作为嵌入式系统,最为紧缺的资源就是存储空间。精简嵌入式系统所使用的库是减小存储空间最常用的方法之一。GNU的Glibc是一个非常宠大而完整的库,至少对于嵌入式系统来说,其体积显得过于大了一些。uClibc的提出较好的解决了这样一个问题。uClibc尽可能的兼容Glibc,大多数应用程序可以在很小或完全不修改的情况下就可能使用uClibc替代glibc。通过uClibc来代替Glibc,可以在不改变应用程序功能的前提下,大大减少发布文件的大小,无论应用程序以静态链接来编译,还是以动态链接形式编译。
不过使用uClibc代替并不是简单的设置一两个参数就行了,通常需要使用一个不同的工具集(gcc/binutils等)来编译代源码。手工的构造这样一个环境,对于大多数普通程序员来说,不一定是一件很简单的事情,因此,uClibc的开发者创造出一个叫做buildroot的工具集。
buildroot将自动构造编译基于uClibc代码的工具集和uClibc库,并提供一个可配置的框架和一些构建一个基本系统的配置文件。用户只需要通过配置菜单选择了相应的目标软件,buildroot就可以从构建基本工具集开始,一直到最后构建出目标系统所需要的东西,如嵌入式系统常用的基于ext2的initrd,jffs根文件系统,压缩的根目录树等,这些代码都是基于uClibc而不是系统的Glibc的。Buildroot对主机系统的要求较小,通常只需要主机系统提供足以构建工具链(toolchain)的工具,如gcc/binutils等,当工具链编译完成后,对目标系统需要的源码的编译过程与主机系统的开发工具集基本上就没有什么关系了。因此,不同的主机如果能够通过第一步,编译完成工具链,那么编译出来的目标系统的执行代码就可以几乎不存在由于系统引起的差异。这样,开发人员就可能在各自喜欢的Linux发行版上进行开发,而不必担心出现什么兼容性问题。
1.1.2 下载相关资源
下载http://buildroot.uclibc.org/downloads/old/buildroot-0.9.27.tar.gz,创建/home/br目录,将buildroot-0.9.27.tar.gz拷贝到该目录,执行下面的命令解压缩,得到目录buildroot。
tar –zxvf buildroot-0.9.27.tar.gz
进入buildroot目录。buildroot默认情况情况下使用wget从互联网下载所需的相关资源,在这里,创建dl目录,作为资源的存放目录。
cd buildroot
mkdir dl
接下来,需要下载http://www.uclibc.org/downloads/uClibc-0.9.27.tar.bz2,并在http://buildroot.uclibc.org/downloads/buildroot-sources/下载ccache-2.3.tar.gz、binutils-2.15.91.0.2.tar.bz2、genext2fs_1.3.orig.tar.gz、linux-libc-headers-2.4.27.tar.bz2。最后还需下载http://ftp.gnu.org/gnu/gcc/gcc-3.3.4/gcc-3.3.4.tar.bz2,一共六个文件,全部拷贝到刚才创建的dl目录。
1.1.3 配置
返回buildroot目录,运行make menuconfig命令配置buildroot,见下图:
图 1-1 buildroot配置界面
Target Architecture(arm) —>选择arm,因为目标板CPU为AT91RM9200,为ARM 920T系列。
Build options —>
()wget command 因为所有的工具已经下载到dl本地目录,故不需要从互联网下载资源,将此项清空。
[ ]Tar verbose
($BUILD_DIR/staging_dir) Toolchain and header file location?这个选项表明编译生成的交叉编译器所在目录,不用修改。
(1) Number of jobs to run simultaneously。
Toolchain Options —>
— Kernel Header Options
Kernel Headers (Linux 2.4.27 kernel headers) —> 注意内核头文件版本和目标板将使用的内核版本一致。我的目标板是2.4.19,使用了一个较为接近的2.4.27替代,目前看来没有太大的问题。
— uClibc Options
[ ] Use daily snapshot of uClibc?是否使用最新版本的uClibc。
[ ] Enable local/gettext/i18n support?
— Binutils Options
Bintuils Version (bintuils 2.15.91.0.2) —>
— Gcc Options
GCC compiler Version (gcc 3.3.4) –>
( ) Additional gcc options
[ * ] Build/install c++ compiler and libstdc++>
[ ] Build/install java compiler and libgcj?
— Cache Options
[ * ] Enable ccache support? 不启用ccache的支持,否则将来在制作交叉编译器时可能因为符号链接而提出头文件路径出错的问题。
— Gdb Options
[ ] Build gdb degugger for the Target
[ ] Build gdb server for the Target
— Common Toolchain Options
[ ] Enable multilib support?
[ ] Enable large file (files > 2GB) support?
[ ] Use software floating point by default
(-Os –pipe) Target Optimizations
Package Selection for the target —> 这一项没有选择任意一项,busybox我是手工完成的,并不打算让buildroot自动构建。
Target Options —>
[ ] cr amfs root filesystem for the target device
[ * ] ext2 root filesystem for the t arget device
[ ] jffs2 root filesystem for the target device
[ ] squashfs root filesystem for the target device
最后退出配置界面,并选择保存设置。
1.1.4 编译
执行make命令编译buildroot,在编译过程中有三个提示,首选是询问ARM版本,选择920T,然后询问端模式,选择Little Endian,由于9200是带有MMU的,所以询问MMU时选择Y。
编译过程在我的VMWare虚拟中持续了大半个小时。
我们需要的交叉编译器现在就在build_arm/staging_dir目录下。这下面最关心的三个目录:bin、lib、include。bin目录包含了所有的交叉编译器,我们关注的是以arm-linux-uclibc-开头的这一组编译器。lib目录是uclibc编译的结果,inlcude是2.4.27内核头文件。
3.3.4的交叉编译器就算完成了。交叉编译器的制作涉及的工具很多,编译的时间很长,非常考验一个人的耐心。下一步,将利用这个交叉工具链去编译busybox。
1.2 利用busybox制作二进制工具
1.2.1 busybox简介
BusyBox 是很多标准 Linux? 工具的一个单个可执行实现。BusyBox 包含了一些简单的工具,例如 cat 和 echo,还包含了一些更大、更复杂的工具,例如 grep、find、mount 以及 telnet(不过它的选项比传统的版本要少);有些人将 BusyBox 称为 Linux 工具里的瑞士军刀。本文将探索 BusyBox 的目标,它是如何工作的,以及为什么它对于内存有限的环境来说是如此重要。
BusyBox 的诞生BusyBox 最初是由 Bruce Perens 在 1996 年为 Debian GNU/Linux 安装盘编写的。其目标是在一张软盘上创建一个可引导的 GNU/Linux 系统,这可以用作安装盘和急救盘。一张软盘可以保存大约 1.4-1.7MB 的内容,因此这里没有多少空间留给 Linux 内核以及相关的用户应用程序使用。
BusyBox 揭露了这样一个事实:很多标准 Linux 工具都可以共享很多共同的元素。例如,很多基于文件的工具(比如 grep 和 find)都需要在目录中搜索文件的代码。当这些工具被合并到一个可执行程序中时,它们就可以共享这些相同的元素,这样可以产生更小的可执行程序。实际上, BusyBox 可以将大约 3.5MB 的工具包装成大约 200KB 大小。这就为可引导的磁盘和使用 Linux 的嵌入式设备提供了更多功能。我们可以对 2.4 和 2.6 版本的 Linux 内核使用 BusyBox。
为了让一个可执行程序看起来就像是很多可执行程序一样,BusyBox 为传递给 C 的 main 函数的参数开发了一个很少使用的特性。回想一下 C 语言的 main 函数的定义如下:
int main( int argc, char *argv[] )
在这个定义中,argc 是传递进来的参数的个数(参数数量),而 argv 是一个字符串数组,代表从命令行传递进来的参数(参数向量)。argv 的索引 0 是从命令行调用的程序名。利用这个性质,我们可以创建一个到可执行程序的符号链接,在执行这个符号链接时,这个符号链接的名字就是argv[0]。
BusyBox 使用了符号链接以便使一个可执行程序看起来像很多程序一样。对于 BusyBox 中包含的每个工具来说,都会这样创建一个符号链接,这样就可以使用这些符号链接来调用 BusyBox 了。BusyBox然后可以通过 argv[0] 来调用内部工具。
1.2.2 下载资源
下载http://www.busybox.net/downloads/busybox-1.00.tar.bz2,创建/home/busybox目录,将busybox-1.00.tar.bz2拷贝到该目录,执行tar –jxvf busybox-1.00.tar.bz2解压缩,得到busybox-1.00目录。进入该目录,下一步就是配置busybox并编译和安装了。
1.2.3 配置和安装
在busybox-1.00目录中执行make menuconfig命令进入busybox的配置界面,见下图:
图 1-2 busybox配置界面
由于选项较多,只罗列主要选项,每有提高的采用默认选项:
General Configuration —>
Buffer allocation policy(Allocate on the Stack) —>
[ * ] Show verbose applet usage messages
[ * ] Support for devfs
[ * ] Use the devpts filesystem for Unix98 PTYs
Build Options —>
[ ] Build BusyBox as a static binary (no share libs) 这一项不选,即采用动态库德方式,可 以节省几百K的空间。
[ * ] Do you want to build BusyBox with a Cross Compiler?
(/home/br/buildroot/build_arm/staging_dir/bin/arm-linux-uclibc-) Cross Compiler 指定交叉编译器,即为利用buildroot构建的交叉编译器路径。
[ * ] Don’t use /usr 这一项一定要选中,不然会覆盖/usr下面的库。
(./_install) BusyBox installation prefix 安装路径,执行make install后会在busybox-1.00目录下生成_install目录,包含了busybox编译结果和相关的工具符号连接。
Archival Utilities —> 主要涉及文档压缩和解压缩的相关工具,保持默认选项即可。
Editors —> 去掉vi,基本上不再嵌入式目标板上运行vi编辑器。
Finding Utilities —>
Init Utilities —> 一定要选中init选项,其它保持默认。
Login/Password Management Utilities —> 选中gettty,事实上,还需要mingetty,不过busybox不支持,只有后面手工编译并加入到文件系统中。
Miscellaneous Utilities —>
Linux Module Utilities —> 动态模块加载相关,动态加载模块给应用程序调试带来很大的方便,所以选择支持,因为内核是2.4版本,所以该项选中以下选项:
[ * ] insmod
[ * ] Support version 2.2.x to 2.4.x Linux kernels
[ * ] lsmod
[ * ] modprobe
[ * ] rmmod
[ * ] Support tainted module checking with new kernels
Another Bourne-like Shell —> 选中ash作为默认的bash,这可以节省很多空间。
Linux System Utilities —>
为了支持NFS,注意将以下三项选中
[ * ] mount
[ * ] Support mounting NFS file system
[ * ] umount
loop设备使用较多,这里也选中
[ * ] Support for loop devices
Debugging Options —> 不需要调试符号
配置结束后保存设置,退出配置界面。
1.2.4 编译安装
执行make编译busybox,编译结束后执行make install,则会在busybox-1.00目录下生成_install目录,其中又包含了bin和sbin两个子目录。两个子目录中除了busybox文件外,其他均是指向该文件的符号连接。
到这一步,busybox已经编译完了,接下来就是构建根文件系统,因为该文件系统是基于uclibc构建的,我将其称之为ucfs。
1.3 构建ucfs
1.3.1 利用loop设备创建文件系统
进入/tmp目录,并创建/mnt/ucfs目录作为将来的挂载位置。执行dd if=/dev/zero of=ucfs bs=1k count=15360,创建一个15M的文件,该文件全部被初始化为0。如果反复在一个文件系统上执行增加或删除操作,会使本来没有使用的部分也难以压缩,将未使用部分初始化为0能够使该部分被压缩。
使用losetup命令将loop设备和ucfs文件关联:losetup –e none /dev/loop0 ucfs。如果loop0已被使用,可以使用loop1,loop2等。
在loop设备上创建ext2文件系统:mke2fs -m 0 /dev/loop0 15360。-m 0的作用是不为root保留使用空间,从而提高文件系统的利用率。
注意:如果找不到losetup和mke2fs命令的话,请在前面加上/ sbin/n 就可以了。
加载文件系统:mount -t ext2 /dev/loop0 /mnt/ucfs。
接下来进入/mnt/ucfs目录,可以看到里面已经有了一个lost+found目录。在/mnt/ucfs目录中添加相关的文件和目录(接下来马上讲解),完成根文件系统的构建。
1.3.2 安装二进制工具、库及其他
在上一小节最后提高的卸载/dev/loop0之前,首先需要在/mnt/ucfs中添加用于制作根文件系统的目录及相关文件。先拷贝二进制工具,即busybox工具集。cp –a /home/busybox/busybox-1.00/_install/* /mnt/ucfs,将busybox和相关的符号连接拷贝到ucfs,观察/mnt/ucfs,增加了bin和sbin目录,删除linuxrc符号连接。
接下来,拷贝交叉编译器中的uclibc库。进入交叉编译器的lib目录,cd /home/br/buildroot/build_arm/staging_dir/lib。然后拷贝必要的库。cp -a *-*.so /mnt/ucfs/lib,cp -a *.so.[*0-9] /mnt/ucfs/lib。回到/mnt/ucfs/lib目录,删除libstdc++,暂时不用c++的标准库,
rm –f libstdc++*。
接下来,利用busybox的示例来构建/etc目录。进入/home/busybox/busybox-1.00/example/bootfloppy目录。拷贝etc目录到/mnt/ucfs,cp –r /etc /mnt/ucfs。关于创建根文件系统,可以仔细阅读bootfloppy.txt文件,很有参考价值。在这里,需要修改busybox默认创建的inittab文件,vi /mnt/ucfs/etc/inittab,将最后两行删除,只保留以下两行:
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
最后还需创建空目录,/mnt/ucfs/proc。至此,根文件系统制作完毕。最后离开/mnt/ucfs以便卸载loop0设备。执行umount /dev/loop0和losetup –d /dev/loop0,使修改保存到ucfs。这个ucfs由gzip压缩后就可以作为系统的根文件系统了。使用gzip –v9 ucfs压缩后,ucfs.gz只有407304字节,不到400K!
最后来看一看成果,见下图。大功告成!
图 1-3 自制文件系统启动界面
嵌入式Linux启动脚本整理
Mar 16th
这几周一直在研究目标机上Linux系统启动过程,先将涉及到的脚本内容及启动具体操作整理一下。
系统启动参数设定启动脚本为/linuxrc,内容如下:
#!/bin/bash
echo “mount /etc as ramfs”
#/bin/mount -n -t ramfs ramfs /etc
#/bin/cp -a /mnt/etc/* /etc
echo “re-create the /etc/mtab entries”
# re-create the /etc/mtab entries
/bin/insmod /usr/sd_mod.ko //加载驱动
/bin/insmod /usr/usb-storage.ko
# /bin/insmod /usr/videodev.o
/bin/insmod /usr/usbvideo.ko
/bin/insmod /usr/ov511.ko
/bin/mknod /dev/video0 c 81 0
/bin/mount -f -t cramfs -o remount,ro /dev/bon/2 /
/bin/mount -t ramfs ramfs /var
/bin/mkdir -p /var/tmp
/bin/mkdir -p /var/run
/bin/mkdir -p /var/log
/bin/mkdir -p /var/lock
/bin/mkdir -p /var/empty
#/bin/mount -t usbdevfs none /proc/bus/usb
exec /sbin/init
接下来启动init进程,init进程是系统的第一个进程,它的PID为1,是所有进程的父进程。Init进程将用到系统引导配置文件/etc/inittab中的信息,根据该信息完成操作系统初始化工作。
# This is run first except when booting
::sysinit:/etc/init.d/rcS // ①
# Start an “askfirst” shell on the console
#::askfirst:-/bin/bash
::askfirst:/bin/bash
# Stuff to do when restarting the init process
::restart:/sbin/init
#::once:/sbin/raja.sh
#::respawn:/sbin/iom
::once:/usr/etc/rc.local // ②
# Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
① /etc/init.d/rcS内容:
#!/bin/bash
/bin/mount -a
/usr/etc/profile
/************************** profile内容*********************
#!/bin/bash
DISPLAY=unix:0.0
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
LD_LIBRARY_PATH=/lib:/usr/lib:/Qtopia/qt-2.3.2/lib
#QWS_KEYBOARD=TTY
LANG=C
HOME=/tmp //路径设置
KMOD=/lib/modules/2.4.19-rmk4-pxa2
export PATH LD_LIBRARY_PATH QTDIR LINUETTEDIR QWS_KEYBOARD LANG HOME DISPLAY KDEDIR KMOD
***********************************************************/
#echo “make /var,/tmp in /dev/shm”
#mkdir /dev/shm/tmp
#cp -a /etc/var /dev/shm
grep -q ‘dnmode=multi’ /proc/cmdline 2>/dev/null // grep为查找函数,不太明白
if [ "$?" = "0" ]; then
/bin/canna
exit 0
fi
grep -q ‘dnmode=single’ /proc/cmdline 2>/dev/null
if [ "$?" = "0" ]; then
/bin/canna -s
exit 0
fi
grep -q ‘resetmode=hard’ /proc/cmdline 2>/dev/null
② /usr/etc/rc.local内容,用户可以把自己的初始化脚本程序放在这里:
#!/bin/bash
. /usr/etc/profile
/sbin/ifconfig lo 127.0.0.1 up
/sbin/ifconfig eth0 192.168.2.223 netmask 255.255.255.0 up
/bin/route add default gw 192.168.2.1 eth0
/sbin/inetd
/usr/sbin/makelinks
source /.bashrc
/bin/cp -rf /Qtopia/qtopia-free-1.7.0/wjluv/* /tmp/
# /bin/cp -rf /usr/config/* /tmp/
/bin/boa -c /home/httpd
/bin/mkdir /tmp/udisk
/bin/mkdir /tmp/images
/bin/mkdir /tmp/flashdisk
/bin/mkdir /tmp/sdcard
/bin/mkdir /tmp/mplayer
#/bin/mount -t yaffs /dev/mtdblock/3 /tmp/flashdisk/ //可挂载FLASH
#/bin/mount -t yaffs /dev/nfblock/4 /tmp/mplayer/
if test -e ”/tmp/flashdisk/userconfig”; then
/bin/chmod u+x /tmp/flashdisk/userconfig
. /tmp/flashdisk/userconfig
fi
. /testshell
# /Qtopia/demo/bin/transerver 1800 -stopplay
./testshell内容,qpe启动。
#!/bin/sh
if test -e ”/dev/input/mouse0″; then
export QWS_MOUSE_PROTO=MouseMan:/dev/input/mice
exec /Qtopia/qtopia-free-1.7.0/bin/qpe-mouse -qws
else
exec /Qtopia/qtopia-free-1.7.0/bin/qpe -qws
fi
from:http://hi.baidu.com/yulanding/blog/item/6e790ecd700bc536f9dc617a.html
常见的几种存储器结构
Mar 16th
1. 平面单一空间存储(Flat Single Space):
平面单一空间存储结构是最简单的结构。每个存储单元都对应一个地址,每个地址都标记一个唯一的存储单元。
2.分段存储(Segmented)
分段存储地址分配的思想也很简单。地址分成2部分:段地址和偏移量。大多数时候只需要使用偏移量,高位附加地址存储在一个或多个特定的段地址寄存器中;若要表示更大范围内的存储地址,则需要在段地址寄存器中重新写入一个值。
使用分段存储,需要引入near和far的概念。要访问一个near对象,即直接使用当前段地址寄存器的值,速度比较快;而要访问一个far对象,就需要改变相关寄存器的值,速度比较慢。
典型的例子:Intel 8086
3.分组切换存储(Bank Switched)
要实现分组切换的存储结构,需包含2部分:存储器地址和一个控制寄存器。前者代表更大存储空间的一个小窗口,后者实现该窗口的移动。采用分组切换存储时,若要访问新的存储器窗口空间,需要先修改控制寄存器中的窗口设置。
4.多空间存储(Multiple Space)
Intel 8051系列使用了映射到片内和片外的ROM和RAM等不同类型存储器的多地址空间。这些存储器的地址分配可能会不明确(一个给定的地址可能对应几个实际的存储单元,而实际上只应该有一个存储单元被正确寻址),所以需要在高级语言(如c语言)中添加特定的数据类型指示,以适应地限定变量的分配空间。
C语言高效编程的几招
Mar 13th
编写高效简洁的C语言代码,是许多软件工程师追求的目标。本文就工作中的一些体会和经
验做相关的阐述,不对的地方请各位指教。
第1招:以空间换时间
计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程
序的效率问题,我们就有了解决问题的第1招–以空间换时间。
例如:字符串的赋值。
方法A,通常的办法:
#define LEN 32
char string1 [LEN];
memset (string1,0,LEN);
strcpy (string1,”This is an example!!”
方法B:
const char string2[LEN]=”This is an example!”
char*cp;
cp=string2;
(使用的时候可以直接用指针来操作。)
从上面的例子可以看出,A和B的效率是不能比的。在同样的存储空间下,B直接使用指针就
可以操作了,而A需要调用两个字符函数才能完成。B的缺点在于灵活性没有A好。在需要频
繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法B,则需要预存许多字
符串,虽然占用了 大量的内存,但是获得了程序执行的高效率。
如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。
该招数的边招–使用宏函数而不是函数。举例如下:
方法C:
#define bwMCDR2_ADDRESS 4
#define bsMCDR2_ADDRESS 17
int BIT_MASK (int_bf)
{
return ((IU<<(bw##_bf))-1)<<(bs##_bf);
}
void SET_BITS(int_dst,int_bf,int_val)
{
_dst=((_dst) & ~ (BIT_MASK(_bf)))I\ (((_val)<<<(bs##_bf))&(BIT_MASK(_bf)))
}
SET_BITS(MCDR2,MCDR2_ADDRESS,RegisterNumber);
方法D:
#define bwMCDR2_ADDRESS 4
#define bsMCDR2_ADDRESS 17
#define bmMCDR2_ADDRESS BIT_MASK (MCDR2_ADDRESS)
#define BIT_MASK(_bf)(((1U<<(bw##_bf))-1)<< (bs##_bf)
#define SET_BITS(_dst,_bf,_val)\ ((_dst)=((_dst)&~(BIT_MASK(_bf)))I (((_val)<<
(bs##_bf))&(BIT_MASK(_bf))))
SET_BITS(MCDR2,MCDR2_ADDRESS,RegisterNumber);
函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道
的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函
数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU也要在函数调用时保存和恢复当
前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。而宏函数不存在这个
问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是
占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。
D方法是我看到的最好的置位操作函数,是ARM公司源码的一部分,在短短的三行内实现了
很多功能,几乎涵盖了所有的位操作功能。C方法是其变体,其中滋味还需大家仔细体会。
第2招:数学方法解决问题
现在我们演绎高效C语言编写的第二招–采用数学方法来解决问题。
数学是计算机之母,没有数学的依据和基础,就没有计算机的发展,所以在编写程序的时
候,采用一些数学方法会对程序的执行效率有数量级的提高。
举例如下,求1~100的和。
方法E
int I,j;
for (I=1; I<=100; I++){
j+=I;
}
方法F
int I;
I=(100*(1+100))/2
这个例子是我印象最深的一个数学用例,是我的饿计算机启蒙老师考我的。当时我只有小
学三年级,可惜我当时不知道用公式Nx(N+1)/2来解决这个问题。方法E循环了100次才解决
问题,也就是说最少用了100个赋值、100个判断、200个加法(I和j);而方法F仅仅用了1个
加法、1个乘法、1次除法。效果自然不言而喻。所以,现在我在编程序的时候,更多的是
动脑筋找规律,最大限度地发挥数学的威力来提高程序运行的效率。
第3招:使用位操作
实现高效的C语言编写的第三招–使用位操作,减少除法和取模的运算。
在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成
所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活
的位操作可以有效地提高程序运行的效率。举例台如下:
方法G
int I,J;
I=257/8;
J=456%32;
方法H
int I,J;
I=257>>3;
J=456-(456>>4<<4);
在字面上好象H比G麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法 G调用了
基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;而方
法H则仅仅是几句相关的汇编,代码更简洁、效率更高。当然,由于编译器的不同,可能效
率的差距不大,但是,以我目前遇到的MS C,ARM C来看,效率的差距还是不小。相关汇编
代码就不在这里列举了。
运用这招需要注意的是,因为CPU的不同而产生的问题。比如说,在PC上用这招编写的程
序,并在PC上调试通过,在移植到一个16位机平台上的时候,可能会产生代码隐患。所以
只有在一定技术进阶的基础下才可以使用这招。
第4招:汇编嵌入
高效C语言编程的必杀技,第四招–嵌入汇编。
“在熟悉汇编语言的人眼里,C语言编写的程序都是垃圾”。这种说法虽然偏激了一些,但
是却有它的道理。汇编语言是效率最高的计算机语言,但是,不可能靠着它来写一个操作
系统吧?所以,为了获得程序的高效率,我们只好采用变通的方法–嵌入汇编、混合编
程。
举例如下,将数组一赋值给数组二,要求每一个字节都相符。char string1[1024],
string2[1024];
方法I
int I;
for (I=0; I<1024; I++)
*(string2+I)=*(string1+I)
方法J
#int I;
for(I=0; I<1024; I++)
*(string2+I)=*(string1+I);
#else
#ifdef_ARM_
_asm
{
MOV R0,string1
MOV R1,string2
MOV R2,#0
loop:
LDMIA R0!,[R3-R11]
STMIA R1!,[R3-R11]
ADD R2,R2,#8
CMP R2, #400
BNE loop
}
#endif
方法I是最常见的方法,使用了1024次循环;方法J则根据平台不同做了区分,在ARM平台
下,用嵌入汇编仅用128次循环就完成了同样的操作。这里有朋友会说,为什么不用标准的
内存拷贝函数呢?这是因为在源数据里可能含有数据为0的字节,这样的话,标准库函数会
提前结束而不会完成我们要求的操作。这个例程典型应用于LCD数据的拷贝过程。根据不同
的CPU,熟练使用相应的嵌入汇编,可以大大提高程序执行的效率。
虽然是必杀技,但是如果轻易使用会付出惨重的代价。这是因为,使用了嵌入汇编,便限
制了程序的可移植性,使程序在不同平台移植的过程中,卧虎藏龙、险象环生!同时该招
数也与现代软件工程的思想相违背,只有在迫不得已的情况下才可以采用。切记。
使用C语言进行高效率编程,我的体会仅此而已。在此已本文抛砖引玉,还请各位高手共同
切磋。希望各位能给出更好的方法,大家一起提高我们的编程技巧。
华中科技大学 丁学
嵌入式linux启动信息完全注释(二)
Feb 22nd
第二部分 : linux内核初始化以及启动
第一节:start_kernel
Linux的源代码可以从www.kernel.org得到,或者你可以查看linux代码交叉引用网站:http://lxr.linux.no/ 进行在线的代码查看,这是一个很好的工具网站。
在start_kernel中将调用到大量的init函数,来完成内核的各种初始化。如:
page_address_init();
sched_init();
page_alloc_init();
init_IRQ();
softirq_init();
console_init();
calibrate_delay();
vfs_caches_init(num_physpages);
rest_init();
具体内容可以参考[http://lxr.linux.no/source/init/main.c]
| Linux version 2.4.22-uc0 (root@local) (gcc version 2.95.3 20010315 (release)) #33 .?1.. 20 12:09:106 |
上面的代码输出信息,是跟踪linux代码分析后得到的,进入init目录下的main.c的start_kernel启动函数.
嵌入式linux使用的是linux内核版本为2.4.22
linux source code代码中start_kernel中输出的linux_banner信息。这个信息是每个linux kernel都会打印一下的信息,如果你没有把这句去掉的话。
| Found bootloader memory map at 0x10000fc0. |
bootloader经过内存映射后的地址为:0x10000fc0, 按上面的地址换算方法,1后面有7个0,那么虚拟地址256M左右处。
| Processor: ARM pt110 revision 0 |
pT110是ARM微处理器arm核的一种,另一种为pT100。此处为显示ARM的类型。
| On node 0 totalpages: 20480
zone(0): 20480 pages. zone(0): Set minimum memory threshold to 12288KB Warning: wrong zone alignment (0×90080000, 0x0000000c, 0×00001000) zone(1): 0 pages. zone(2): 0 pages. |
预留内存大小,在节点0上总共20页, zone(0) 设置最小内存为12MB, zone(1)和zone(2)为0页。警告:对齐不正确
| Kernel command line: root=/dev/mtdblock3 |
Kernel 启动命令设为:/dev/mtdblock3(在后面的说明中会看到mtdblock3是指的flash上的romfs分区。),用来指定根文件系统所在的位置,kernel会将块设备mtdblock3当作文件系统来处理。
也就是说,内核会根据上面的kernel命令行,知道只读文件系统romfs将是根文件系统rootfs。
start_kernel(void)中输出的上面的这句信息。
这行命令是在linux内核启动过程中都会输出的一句。
| Console: colour dummy device 80×30 |
代码中console_init()的输出信息, 显示控制台属性:一般使用VGA text console,标准是80 X 25行列的文本控制台,这里是对属性进行了设置。
| serial_xx: setup_console @ 115 |
串口设置值为115200,此为波特率输出信息。对串口设置的信息做一个打印的动作,在调试时会非常有用。
| Calibrating delay loop… 82.94 BogoMIPS |
Calibrate:校准, 进入时延校准循环。检查CPU的MIPS(每秒百万条指令),Bogo是Bogus(伪)的意思。这里是对CPU进行一个实时测试,来得到一个大体的MIPS数值
Bogomips,是由linus Torvalds写的, 是Linux操作系统中衡量计算机处理器运行速度的一种尺度。提供这种度量的程序被称为BogoMips,当启动计算机时,BogoMips能显示系统选项是否处于最佳性能。
linux内核中有一个函数calibrate_delay(),它可以计算出cpu在一秒钟内执行了多少次一个极短的循环,计算出来的值经过处理后得到BogoMIPS值
你可以将计算机的bogomips与计算机处理器的bogomips进行比较。Torvalds称这个程序为BogoMips来暗示两台计算机间的性能度量是错误的,因为并非所有起作用因素都能被显示出来或被认可。尽管计算机基准中经常用到MIPS,但环境的变化容易导致度量的错误。Bogomips能测出一秒钟内某程序运行了多少次。
察看/proc/cpuinfo文件中的最后一行也能得到这个数值。
上面这个输出,在所有的linux系统启动中都会打印出来。
进入内存初始化
mem_init(void), [arch/i386/mm/init.c]
| Memory: 80MB = 80MB total
Memory: 76592KB available (1724K code, 2565K data, 72K init) |
当前内存使用情况,将列出总的内存大小, 及分配给内核的内存大小:包括代码部分,数据部分,初始化部分,总共刚好4M。请留意此处的内核的内存大小的各个值。
进入虚拟文件系统VFS初始化
vfs_caches_init()
| Dentry cache hash table entries: 16384 (order: 5, 131072 bytes)
Inode cache hash table entries: 8192 (order: 4, 65536 bytes) Mount cache hash table entries: 512 (order: 0, 4096 bytes) Buffer cache hash table entries: 4096 (order: 2, 16384 bytes) Page-cache hash table entries: 32768 (order: 5, 131072 bytes) |
名词:
① Dentry:目录数据结构
② Inode:i节点
③ Mount cache:文件系统加载缓冲
④ buffer cache:内存缓冲区
⑤ Page Cache:页缓冲区
Dentry目录数据结构(目录入口缓存),提供了一个将路径名转化为特定的dentry的一个快的查找机制,Dentry只存在于RAM中;
i节点(inode)数据结构存放磁盘上的一个文件或目录的信息,i节点存在于磁盘驱动器上;存在于RAM中的i节点就是VFS的i节点,dentry所包含的指针指向的就是它;
buffer cache内存缓冲区,类似kupdated,用来在内存与磁盘间做缓冲处理;
Page Cache 用来加快对磁盘上映像和数据的访问。
在内存中建立各个缓冲hash表,为kernel对文件系统的访问做准备。
VFS(virtual filesystem switch)虚拟文件切换目录树有用到类似这样的结构表。
上面的输出信息,在一般的linux启动过程中都会看到。
| POSIX conformance testing by UNIFIX |
conformance:顺应, 一致。即POSIX适应性检测。UNIFIX是一家德国的技术公司,Linux 原本要基于 POSIX.1 的, 但是 POSIX 不是免费的, 而且 POSIX.1 证书相当昂贵. 这使得 Linux 基于 POSIX 开发相当困难. Unifix公司(Braunschweig, 德国) 开发了一个获得了 FIPS 151-2 证书的 Linux 系统. 这种技术用于 Unifix 的发行版 Unifix Linux 2.0 和 Lasermoon 的 Linux-FT。
在2.6的内核中就将上面的这句输出给拿掉了。
第二节:用户模式( user_mode )开始,start_kernel结束
| PCI: bus0: Fast back to back transfers disabled
PCI: Configured XX as a PCI slave with 128MB PCI memory PCI: Each Region size is 16384KB PCI: Reserved memory from 0×10080000 to 0×15080000 for DMA and mapped to 0×12000000 |
设备的初始化 init()—>do_basic_init()—>pci_init(),初始化PCI,检测系统的PCI设备。
| Linux NET4.0 for Linux 2.4
Based upon Swansea University Computer Society NET3.039 |
英国威尔士,斯旺西大学的NET3.039, TCP/IP 协议栈
此信息,在linux启动过程中都会出现。
| Initializing RT netlink socket |
对Socket的初始化,socket_init(),Netlink 一种路由器管理协议(linux-2.4.22\net\core\Rtnetlink.c,Routing netlink socket interface: protocol independent part。 其中RT是route路由的意思。这句输出是在create产生rtnetlink的socket套接字时的一个调试输出。)
此信息,在linux启动过程中都会出现。
| Starting kswapd |
启动交换守护进程kswapd,进程IO操作例程kpiod
kswapd可以配合kpiod运行。进程有时候无事可做,当它运行时也不一定需要把其所有的代码和数据都放在内存中。这就意味着我们可以通过把运行中程序不用的内容切换到交换分区来更好的是利用内存。大约每隔1秒,kswapd醒来并检查内存情况。如果在硬盘的东西要读入内存,或者内存可用空间不足,kpiod就会被调用来做移入/移出操作。kswapd负责检查,kpiod负责移动。
| Journalled Block Device driver loaded |
加载日志块设备驱动。
日志块设备是用来对文件系统进行日志记录的一个块设备。日志文件系统是在传统文件系统的基础上,加入文件系统更改的日志记录。
它的设计思想是:跟踪记录文件系统的变化,并将变化内容记录入日志。日志文件系统在磁盘分区中保存有日志记录,写操作首先是对记录文件进行操作,若整个写操作由于某种原因(如系统掉电)而中断,系统重启时,会根据日志记录来恢复中断前的写操作。在日志文件系统中,所有的文件系统的变化都被记录到日志,每隔一定时间,文件系统会将更新后的元数据及文件内容写入磁盘。在对元数据做任何改变以前,文件系统驱动程序会向日志中写入一个条目,这个条目描述了它将要做些什么,然后它修改元数据。
| devfs: v1.12c (20020818) Richard Gooch (rgooch@atnf.csiro.au)
devfs: boot_options: 0×1 |
Devfs模块的输出信息。
设备文件系统devfs,版本1.12c,
| pty: 256 Unix98 ptys configured |
Pty模块的输出信息,与控制台操作有关的设置。
将通过 devpts 文件系统使用 Unix98 PTYs,(Pseudo-ttys (telnet etc) device是伪ttys设备的缩写。
① TTY(/dev/tty)是TeleTYpe的一个老缩写,为用户输入提供不同控制台的设备驱动程序。它的名字来源于实际挂接到UNIX系统的、被称为电传打字机(teletype)的终端。在Linux下,这些文件提供对虚拟控制台的支持,可以通过按<Alt-F1>到<Alt-F6>键来访问这些虚拟控制台。这些虚拟控制台提供独立的、同时进行的本地登录对话过程
② ttys(/dev/ttys)是计算机终端的串行接口。/dev/ttyS0对应MS-DOS下的 COM1。
使用 make dev脚本MAKEDEV来建立pty文件。这样系统内核就支持Unix98风格的pty了。在进行Telnet登录时将要用到/dev/pty设备。 pty是伪终端设备,在远程登录等需要以终端方式进行连接,但又并非真实终端的应用程序中必须使用这种设备,如telnet或xterm等程序。Linux 2.2以后增添了UNIX98风格的Pty设备,它使用一个新的文件系统(devpts针对伪终端的文件系统)和一个克隆的设备cloning device来实现其功能。
linux-2.4.22\drivers\char\Pty.c, 在devfs_mk_dir (NULL, “pts”, NULL);时会输出上面的信息。
| loop: loaded (max 8 devices) |
加载返还块设备驱动,最多支持8个设备
| 8139too Fast Ethernet driver 0.9.27
eth0: RealTek RTL8139 at 0×60112000, 00:10:0d:42:a0:03, IRQ 14 eth0: Identified 8139 chip type ‘RTL-8100B/8139D’ |
网卡驱动,基地址为:0×60112000, MAC地址:00:10:0d:42:a0:03, 中断号:14
RTL8139 的接收路径设计成一个环形缓冲区(一段线性的内存,映射成一个环形内存)。当设备接收到数据时,数据的内容就保存在这个环形缓冲区内并更新下个存储数据的地址(第一个数据包的开始地址+第一个数据包的长度)。设备会一直保留缓冲区内的数据直到整个缓冲区耗尽。这样,设备会再次重写缓冲区内起始位置的内容,就像一个环那样。
从 2.2 版内核升级到 2.4 版时, RTL-8139 支持模块已不再叫 rtl8139,而叫它 8139too,现在你再看到8139too就不会不明白它的来由了吧。
| SCSI subsystem driver Revision: 1.00 |
USB设备信息,USB会被当做SCSI来处理。
| mumk_register_tasklet: (1) tasklet 0x905bf9c0 status @0x9025e974 |
软中断信息输出。Tasklet是在2.4中才出现,它是为了更好地利用多CPU。
| Probing XX Flash Memory |
探测 XX的闪存(Flash Memory),”NOR NAND Flash Memory Technology”
| Amd/Fujitsu Extended Query Table v1.3 at 0×0040
number of CFI chips: 1 |
AMD与富士通合资设立的Flash供货商Spansion。AMD因获利不佳,已经退出Flash市场,后续由Spansion合资公司经营.主要生产NOR类型的flash,特点是容量小,速度快。Spansion商标的flash,在我们开发中会经常看到。以后大家看到Spansion的芯片,就能了解到它和AMD还有富士通的来龙去脉了。
Common flash Interface (CFI)是指一个统一的flash访问接口,表示这种flash是这种接口类型的。
| Using buffer write method |
使用flash写缓冲方式
flash提供了写BUFFER的命令来加快对flash上块的操作。对Flash擦除和写数据是很慢的。如果用写BUFFER的命令会快一点。据手册上说,会快20倍。Buffer Size :5 bytes的buffer缓冲不是每个块都有,是整个flash只有一个5 bytes的buffer,用写BUFFER命令对所有的块进行写操作,都要用同一个buffer,写Buffer是主要检查buffer是否available,其实buffer起缓冲作用,来提高工作效率。
比如某flash有128个128K字节块。允许用户对任意块进行字节编程和写缓冲器字节编程操作,每字节编程时间为210μs;若采用写缓冲器字节编程方式,32字节编程共需218μs,每字节编程时间仅为6.8μs。芯片的块擦除时间为1s,允许在编程或块擦除操作的同时进行悬挂中断去进行读操作,待读操作完成后,写入悬挂恢复命令,再继续编程或块擦除。
| Creating 3 MTD partitions on “XX mapped flash”:
0×00000000-0×00020000 : “BootLoader” 0×00020000-0×00040000 : “Config” 0×00040000-0×01000000 : “Romfs” |
此处为重要信息部分,需要特别留意。
在内存中映射过的flash,创建三个MTD分区:
flash上的内容将被映射到内存中的对应地址
前128K为BootLoader—>0×00000000-0×00020000
接着的128K为系统配置信息Config存放的位置—>0×00020000-0×00040000
再后面的 16M - 2X128K 为romfs的存放处.—>0×00040000-0×01000000
上面的内容,大家可以根据前面的换算公式得到。
A> 编译的bootloader一般大小约50K左右;
B> 在此处就知道了配置信息config是放在第2分区中的;
C> 制作的romfs的大小,一般为8M或10M左右,所以能放得下;
嵌入式Linux内核的块设备驱动:
对于linux 的根文件系统,目前有三种块设备的驱动可以选择,它们分别是:
a) Blkmem 驱动
b) MTD 驱动
c) RAM disk 驱动
Blkmem 驱动是专门为嵌入式linux 开发的一种块设备驱动,它是嵌入式linux系统中最为古老和通用的块设备驱动。它原理相对简单但是配置比较复杂,需要根据你即的Flash的分区使用情况来修改代码。当然修改的结果是它可以对一些NOR型的Flash进行读写操作。不过目前支持的Flash类型不够多。如果新加入对一种Flash的支持需要作的工作量比较大。
Linux的MTD驱动是标准Linux的Flash驱动。它支持大量的设备,有足够的功能来定义Flash的分区,进行地址映射等等。使用MTD你可以在一个系统中使用不同类型的Flash。它可以将不同的Flash组合成一个线性的地址让你来使用。
在标准的Linux 2.4内核中MTD有一系列的选项,你可以根据个人系统的需要来选择,定制。
另外一种选择就是RAM disk 驱动。在PC上它经常用于没有硬盘的Linux的启动过程。它和Flash没有直接的关系。不过当Flash上启动的是经过压缩的内核时。RAM disk 可以作为根文件系统。
MTD 驱动提供了对Flash强大的支持,你通过它甚至可以在Flash上运行一个可以读写的真正的文件系统,比如JFFS2。而Blkmem驱动则望尘莫及。
| NET4: Linux TCP/IP 1.0 for NET4.0 |
调用inet_init [ linux-2.4.22\net\ipv4\Af_inet.c ]时的输出信息, 在启动过程中被socket.c调用到。
| IP Protocols: ICMP, UDP, TCP, IGMP |
列出可以支持的IP协议,此处为kernel源代码inet_add_protocol(p);的输出。
在linux启动过程中,都会看到这句的输出。
| IP: routing cache hash table of 512 buckets, 4Kbytes |
IP路由代码的输出信息。
ip_rt_init [ linux-2.4.22\net\ipv4\Route.c ],Set the IP module up,路由缓冲hash表
| TCP: Hash tables configured (established 8192 bind 8192) |
TCP协议初始化输出信息。tcp_init [ linux-2.4.22\net\ipv4\Tcp.c ],
| NET4: Unix domain sockets 1.0/SMP for Linux NET4.0. |
UNIX网络协议信息。
af_unix_init[ linux-2.4.22\net\unix\Af_unix.c ], 多种连接的一种(IPv4, UNIX domain sockets, IPv6和IrDA). SMP 对称多处理器—Symmetrical Multi Processing,这里主要是指UNIX的一些网络协议.
上面的关于网络的输出信息是在linux启动信息中都会出现的。
加载各种文件系统
| cramfs: wrong magic |
会出现“cramfs: wrong magic”,别担心这没有什么害处,这个是kernel的书写bug,在2.6中有修改之,它是一个警告信息,用来检查cramfs的superblock超级块的。superblock也是VFS要用到的数据结构。
代码linux-2.4.22\fs\cramfs\Inode.c:
| 2.4
cramfs_read_super(。。。) /* Do sanity checks on the superblock */ if (super.magic != CRAMFS_MAGIC) { /* check at 512 byte offset */ memcpy(&super, cramfs_read(sb, 512, sizeof(super)), sizeof(super)); if (super.magic != CRAMFS_MAGIC) { printk(KERN_ERR “cramfs: wrong magic\n”); goto out; } } |
| 2.6
if (super.magic != CRAMFS_MAGIC) { if (!silent) printk(KERN_ERR “cramfs: wrong magic\n”); goto out; } |
超级块是文件系统的“头部”。它包含文件系统的状态、尺寸和空闲磁盘块等信息。如果损坏了一个文件系统的超级块(例如不小心直接将数据写到了文件系统的超级块分区中),那么系统可能会完全不识别该文件系统,这样也就不能安装它了,即使采用e2fsck 命令也不能处理这个问题。
Cramfs文件系统:
cramfs 是 Linus Torvalds 本人开发的一个适用于嵌入式系统的小文件系统。由于它是只读的,所以,虽然它采取了 zlib 做压缩,但是它还是可以做到高效的随机读取。 cramfs 不会影响系统读取文件的速度,又是一个高度压缩的文件系统。
我们制作image文件之后,我们还要考虑怎样才能在系统运行的时候,把这个 image 文件 mount 上来,成为一个可用的文件系统。由于这个 image 文件不是一个通常意义上的 block 设备,我们必须采用 loopback 设备来完成这一任务,如:
mount -o loop -t cramfs /usr.img /usr
这样,就可以经由 loopback 设备,把 usr.img 这个 cramfs 的 image 文件 mount 到 /usr 目录上去了。内核中需要对loopback这个设备的支持。
cramfs 的压缩效率一般都能达到将近 50%。
Cramfs通过优化索引节点表的尺寸和除去传统文件系统中文件之间的空间浪费来达到节约空间的目的。它还使用了zlib压缩,实现优于2:1的压缩比例。解压缩过程的系统开销并不是很大,因为Cramfs支持指定单块的解压,而并不必解压缩整个文件。
Cramfs不仅能节省空间,还能避免非正常关机导致的等待fsck或手工进行fsck的麻烦。它通过只读的方式达到这一目的。
RamDisk有三种实现方式:
在Linux中可以将一部分内存mount为分区来使用,通常称之为RamDisk,分为:
Ramdisk, ramfs, tmpfs.
① 第一种就是传统意义上的,可以格式化,然后加载。
这在Linux内核2.0/2.2就已经支持,其不足之处是大小固定,之后不能改变。
为了能够使用Ramdisk,我们在编译内核时须将block device中的Ramdisk支持选上,它下面还有两个选项,一个是设定Ramdisk的大小,默认是4096k;另一个是initrd的支持。
如果对Ramdisk的支持已经编译进内核,我们就可以使用它了:
首先查看一下可用的RamDisk,使用ls /dev/ram*
首先创建一个目录,比如test,运行mkdir /mnt/test;
然后对/dev/ram0 创建文件系统,运行mke2fs /dev/ram0;
最后挂载 /dev/ram0,运行mount /dev/ram /mnt/test,就可以象对普通硬盘一样对它进行操作了。
② 另两种则是内核2.4才支持的,通过Ramfs或者Tmpfs来实现:
它们不需经过格式化,用起来灵活,其大小随所需要的空间而增加或减少。
Ramfs顾名思义是内存文件系统,它处于虚拟文件系统(VFS)层,而不像ramdisk那样基于虚拟在内存中的其他文件系统(ex2fs)。
因而,它无需格式化,可以创建多个,只要内存足够,在创建时可以指定其最大能使用的内存大小。
如果你的Linux已经将Ramfs编译进内核,你就可以很容易地使用Ramfs了。创建一个目录,加载Ramfs到该目录即可:
# mkdir /testRam
# mount -t ramfs none /testRAM
缺省情况下,Ramfs被限制最多可使用内存大小的一半。可以通过maxsize(以kbyte为单位)选项来改变。
# mount -t ramfs none /testRAM -o maxsize=2000 (创建了一个限定最大使用内存为2M的ramdisk)
③ Tmpfs是一个虚拟内存文件系统,它不同于传统的用块设备形式来实现的Ramdisk,也不同于针对物理内存的Ramfs。
Tmpfs可以使用物理内存,也可以使用交换分区。在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,这些资源是由内核中的虚拟内存子系统来负责分配和管理。
Tmpfs向虚拟内存子系统请求页来存储文件,它同Linux的其它请求页的部分一样,不知道分配给自己的页是在内存中还是在交换分区中。同Ramfs一样,其大小也不是固定的,而是随着所需要的空间而动态的增减。
使用tmpfs,首先你编译内核时得选择”虚拟内存文件系统支持(Virtual memory filesystem support)” 。
然后就可以加载tmpfs文件系统了:
# mkdir -p /mnt/tmpfs
# mount tmpfs /mnt/tmpfs -t tmpfs
同样可以在加载时指定tmpfs文件系统大小的最大限制:
# mount tmpfs /mnt/tmpfs -t tmpfs -o size=32m
| FAT: bogus logical sector size 21072 |
具体的文件系统FAT格式。
虚拟逻辑扇区大小为20K,linux-2.4.22\fs\fat\Inode.c。
在初始化MS-DOS文件系统时,读MS-DOS文件系统的superblock,函数fat_read_super中输出的上面的信息。
| UMSDOS: msdos_read_super failed, mount aborted. |
UMSDOS:一种文件系统,特点容量大但相对而言不大稳定。是Linux 使用的扩展了的DOS文件系统。它在 DOS 文件系统下增加了长文件名、 UID/GID、POSIX 权限和特殊文件 (设备、命名管道等)功能,而不牺牲对 DOS 的兼容性。允许一个普通的msdos文件系统用于Linux,而且无须为它建立单独的分区,特别适合早期的硬盘空间不足的硬件条件。
| VFS: Mounted root (romfs filesystem) readonly |
虚拟文件系统VFS(Virtual Filesystem Switch)的输出信息。
再次强调一下一个概念。VFS 是一种软件机制,也可称它为 Linux 的文件系统管理者,它是用来管理实际文件系统的挂载点,目的是为了能支持多种文件系统。kernel会先在内存中建立一颗 VFS 目录树,是内存中的一个数据对象,然后在其下挂载rootfs文件系统,还可以挂载其他类型的文件系统到某个子目录上。
| Mounted devfs on /dev |
加载devfs设备管理文件系统到dev安装点上。
/dev是我们经常会用到的一个目录。
在2.4的kernel中才有使用到。每次启动时内核会自动挂载devfs。
devfs提供了访问内核设备的命名空间。它并不是建立或更改设备节点,devfs只是为你的特别文件系统进行维护。一般我们可以手工mknod创件设备节点。/dev目录最初是空的,里面特定的文件是在系统启动时、或是加载模组后驱动程序载入时建立的。当模组和驱动程序卸载时,文件就消失了。
| Freeing init memory: 72K |
释放1号用户进程init所占用的内存。
第三节:加载linux内核完毕,转入cpu_idle进程
系统启动过程中进程情况:
①init进程
一般来说, 系统在跑完 kernel bootstrapping 内核引导自举后(被装入内存、已经开始运行、已经初始化了所有的设备驱动程序和数据结构等等), 就去运行 init『万process之父』, 有了它, 才能开始跑其他的进程,因此,init进程,它是内核启动的第一个用户级进程,它的进程号总是1。
| 你可以用进程查看命令来验证
# ps aux PID Uid VmSize Stat Command 1 0 SW init 2 0 SW [keventd] 3 0 SWN [ksoftirqd_CPU0] 4 0 SW [kswapd] 5 0 SW [bdflush] 6 0 SW [kupdated] 7 0 SW [rbwdg] 9 0 SW [mtdblockd] 10 0 SW [khubd] 80 0 SW [loop0] |
另外 Linux 有两个 kernel 类的 process 也开始跑了起来,一个是 kflushd/bdflush,另一个是 kswapd;
只有这个 init 是完全属于 user 类的进程, 后两者是 kernel假借 process 进程之名挂在进程上。
init有许多很重要的任务,比如象启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。
init 一开始就去读 /etc/inittab (init初始化表),初始化表是按一定格式排列的关于进程运行时的有关信息的。init程序需要读取/etc/inittab文件作为其行为指针。这个 inittab 中对于各个runlevel运行级别要跑哪些 rc 或 spawn 生出什么有很清楚的设定。
一般, 在Linux中初始化脚本在/etc/inittab 文件(或称初始化表)中可以找到关于不同运行级别的描述。inittab是以行为单位的描述性(非执行性)文本,每一个指令行都是固定格式
inittab中有respawn项,但如果一个命令运行时失败了,为了避免重运行的频率太高,init将追踪一个命令重运行了多少次,并且如果重运行的频率太高,它将被延时五分钟后再运行。
② kernel进程
A> 请注意init是1号进程,其他进程id分别是kflushd/ bdflush, kupdate, kpiod and kswapd。这里有一个要指出的:你会注意到虚拟占用(SIZE)和实际占用(RSS)列都是0,进程怎么会不使用内存呢?
这些进程就是内核守护进程。大部分内核并不显示在进程列表里。守护进程在init之后启动,所以他们和其他进程一样有进程ID,但是他们的代码和数据都存放在内核占有的内存中。在列表中使用中括号来区别与其他进程。
B> 输入和输出是通过内存中的缓冲来完成的,这让事情变得更快,程序的写入会存放在内存缓冲中,然后再一起写入硬盘。守护进程kflushd和kupdate 管理这些工作。kupdate间断的工作(每5秒)来检查是否有写过的缓冲,如过有,就让kflushd把它们写入磁盘。
C> 进程有时候无事可做,当它运行时也不一定需要把其所有的代码和数据都放在内存中。这就意味着我们可以通过把运行中程序不用的内容切换到交换分区来更好的是利用内存。把这些进程数据移入/移出内存通过进程IO管理守护进程kpiod和交换守护进程kswapd,大约每隔1秒,kswapd醒来并检查内存情况。如果在硬盘的东西要读入内存,或者内存可用空间不足,kpiod就会被调用来做移入/移出操作。
D> bdflush – BUF_DIRTY, 将dirty缓存写回到磁盘的核心守护进程。 对于有许多脏的缓冲区(包含必须同时写到磁盘的数据的缓冲区)的系统提供了动态的响应。它在系统启动的时候作为一个核心线程启动,它叫自己为“kflushd”,而这是你用ps显示系统中的进程的时候你会看得的名字。即定期(5秒)将脏(dirty)缓冲区的内容写入磁盘,以腾出内存;
E> ksoftirqd_CPUx 是一个死循环, 负责处理软中断的。它是用来对软中断队列进行缓冲处理的进程。当发生软中断时,系统并不急于处理,只是将相应的cpu的中断状态结构中的active 的相应的位,置位,并将相应的处理函数挂到相应的队列,然后等待调度时机来临,再来处理.
ksoftirqd_CPUx是由cpu_raise_softirq()即cpu触发中断,唤醒的内核线程,这涉及到软中断,ksoftirqd的代码参见[kernel/softirq.c]
F> keventd,它的任务就是执行 scheduler 调度器队列中的任务,keventd 为它运行的任务提供了可预期的进程上下文。
G> khubd, 是用来检测USB hub设备的,当usb有动态插拔时,将交由此内核进程来处理。在检测到有hub事件时会有相应的动作(usb_hub_events())
H> mtdblockd是用来对flash块设备进行写操作的守护进程。
NAND类型的Flash需要MTD(Memory Technology Devices 内存技术驱动程序)驱动的支持才能被linux所使用。
NAND的特点是不能在芯片内执行(XIP,eXecute In Place),需要把代码读到系统RAM中再执行,传输效率不是最高,最大擦写次数量为一百万次,但写入和擦除的速度很快,擦除单元小,是高数据存储密度的最佳选择。
NAND需要I/O接口,因此使用时需要驱动程序。
I> loop0 是负责处理loop块设备的(回环设备)。loopback device指的就是拿文件来模拟块设备, 在我们这里,loop设备主要用来处理需要mount到板上的文件系统,类似mount /tmp/rootfs /mnt -o loop。.我们的实例有:mount -o loop -t cramfs /xxx.bin /xxx 也就是将xxx.bin这个文件mount到板上来模拟cramfs压缩ram文件系统。loop0进程负责对loop设备进行操作。
loopback设备和其他的块设备的使用方法相同。特别的是,可以在该设备上建立一个文件系统,然后利用mount命令把该系统映射到某个目录下以便访问。这种整个建立在一个普通磁盘文件上的文件系统,就是虚拟文件系统 (virtual file system)。
总结:
上面的内容是本人为了在实际开发中更加清楚地了解嵌入式linux的启动过程而做的一个总结性的文章。
在对嵌入式linux的启动过程做了一个详细注释后,大家会对涉及到嵌入系统的各个概念有了一个更加明确的认识,并能对嵌入系统的软硬件环境的有关设置更加清楚。当你自己动手结合linux源代码来分析时,将会有一个清楚的全局观。
现在,你如果再回头去看文章前面所列出的启动信息例子中的内容,其中80%的内容,你现在应该能看懂它的来龙去脉了。
嵌入式linux启动信息完全注释
Feb 22nd
在某论坛上看到一篇帖子,上面贴着嵌入式linux开发板启动时的有关信息,然后大家在帖子里讨论着这个启动过程中出现的问题,随机举例如下:
| Linux version 2.4.20-uc0 (root@Local) (gcc version 2.95.3 20010315 (release)(ColdFire patches – 20010318 from http://f (uClinux XIP and shared lib patches from http://www.snapgear.com/)) #20 三 6月 1 8 00:58:31 CST 2003 Processor: Samsung S3C4510B revision 6 Architecture: SNDS100 On node 0 totalpages: 4096 zone(0): 0 pages. zone(1): 4096 pages. zone(2): 0 pages. Kernel command line: root=/dev/rom0 Calibrating delay loop… 49.76 BogoMIPS Memory: 16MB = 16MB total Memory: 14348KB available (1615K code, 156K data, 40K init) Dentry cache hash table entries: 2048 (order: 2, 16384 bytes) Inode cache hash table entries: 1024 (order: 1, Mount-cache hash table entries: 512 (order: 0, 4096 bytes) Buffer-cache hash table entries: 1024 (order: 0, 4096 bytes) Page-cache hash table entries: 4096 (order: 2, 16384 bytes) POSIX conformance testing by UNIFIX Linux NET4.0 for Linux 2.4 Based upon Swansea University Computer Society NET3.039 Initializing RT netlink socket Starting kswapd Samsung S3C4510 Serial driver version 0.9 (2001-12-27) with no serial options en abled ttyS00 at 0x3ffd000 (irq = 5) is a S3C4510B ttyS01 at 0x3ffe000 (irq = 7) is a S3C451 Blkmem copyright 1998,1999 D. Jeff Dionne Blkmem copyright 1998 Kenneth Albanowski Blkmem 1 disk images: 0: BE558-1A5D57 [VIRTUAL BE558-1A5D57] (RO) RAMDISK driver initialized: 16 RAM disks of 1024K size 1024 blocksize Samsung S3C4510 Ethernet driver version 0.1 (2002-02-20) <mac@os.nctu.edu.tw> eth0: 00:40:95:36:35:34 NET4: Linux TCP/IP 1.0 for NET4.0 IP Protocols: ICMP, UDP, TCP IP: routing cache hash table of 512 buckets, 4Kbytes TCP: Hash tables configured (established 1024 bind 1024) VFS: Mounted root (romfs Freeing init memory: 40K |
上面的这些输出信息,也可能包括你自己正在做的嵌入式linux开发板的输出信息,其中的每一行,每一个字的含义,你是否深究过,或者说大部分的含义你能确切地知道的?本人想在这里结合本人在实践中一些体会来和广大嵌入式linux的开发者一起读懂这些信息。
我们在这里将以一个真实的嵌入式linux系统的启动过程为例,来分析这些输出信息。启动信息的原始内容将用标记标出,以区别与注释。
嵌入式linux的启动主要分为两个阶段:
① 第一部分bootloader启动阶段
② 第二部分linux 内核初始化和启动阶段
第一节:start_kernel
第二节:用户模式( user_mode )开始,start_kernel结束
第三节:加载linux内核完毕,转入cpu_idle进程
第一部分 : bootloader启动
| Boot loader v0.12
NOTE: this boot loader is designed to boot kernels made with the 2.4.xx releases bootloader for XV Built at Nov 20 2005 10:12:35 |
Bootloader头信息,版本,编译时间等,这个因不同的bootloader的设计而有所不同,由此你能看出bootloader的版本信息,有很多使用的是通用的bootloader,如u-boot,redboot等。
| Loaded to 0×90060000 |
将bootloader加载到内存ram中的0×90060000处,即将bootloader加载到内存的高端地址处。
Linux内核将被bootloader加载到0×90090000处。
| Found boot configuration |
查找到了启动boot的配置信息
| Booted from parallel flash |
从flash中启动代码,此处的flash为并行闪存。Flash的分类列举如下:
闪存分三类:并行,串行,不可擦除。
①并行Parallel flash
NOR Flash,Intel于1988年发明.随机读取的速度比较快,随机按字节写,每次可以传输8Bit。一般适合应用于数据/程序的存贮应用中.NOR还可以片内执行(execute-in-place)XIP.写入和擦除速度很低。
NAND Flash,1989年,东芝公司发明.是以块和页为单位来读写的,不能随机访问某个指定的点.因而相对来说读取速度较慢,而擦除和写入的速度则比较快,每次可以传输16Bit,一般适用在大容量的多媒体应用中,容量大。如:CF,SM.
②串行Serial Flash 是以字节进行传输的,每次可以传输1-2Bit.如:MMC,SD,MS卡.串行闪存器件体积小,引脚也少,成本相对也更低廉。
③不可擦除Mask Rom Flash的特点是一次性录入数据,具有不可更改性,经常运用于游戏和需版权保护文件等的录入。其显著特点是成本低。
注意:任何flash器件的写入操作只能在空或已擦除的单元内进行,所以大多数情况下,在进行写入操作之前必须先执行擦除。NAND器件执行擦除操作是十分简单的,而NOR则要求在进行擦除前先要将目标块内所有的位都写为0。
从上面的信息,我们可以对flash类型特点有个比较明确的了解。
| CPU clock rate: 200 MHz |
开发板上所使用的CPU的主频为200MHZ.
| DRAM size is 128MB (128MB/0MB) |
动态内存ram大小为128M。这里我们列举一下内存的类型及工作原理。
根据内存的工作原理可以划分出两种内存:DRAM和SRAM
①DRAM表示动态随机存取存储器。这是一种以电荷形式进行存储的半导体存储器。DRAM中的每个存储单元由一个晶体管和一个电容器组成。数据存储在电容器中。电容器会由于漏电而导致电荷丢失,因而DRAM器件是不稳定的。为了将数据保存在存储器中,DRAM器件必须有规律地进行刷新。
②SRAM是静态的,因此只要供电它就会保持一个值。一般而言,SRAM 比DRAM要快,这是因为SRAM没有刷新周期。每个SRAM存储单元由6个晶体管组成,而DRAM存储单元由一个晶体管和一个电容器组成。相比而言,DRAM比SRAM每个存储单元的成本要高。照此推理,可以断定在给定的固定区域内DRAM的密度比SRAM 的密度要大。
SRAM常常用于高速缓冲存储器,因为它有更高的速率;而DRAM常常用于PC中的主存储器,因为其拥有更高的密度。
在嵌入式系统中使用DRAM内存的设计比较广泛。
地址辅助说明:
| 先说明一下内存地址数字情况,主要是为了方便记忆。
可以访问的内存为4G。 0×40000000是1GB处;0×00040000是256K处,0×00020000是128K处,0×90000000是2GB多的地方。 1M->0×00100000, 2M->0×00200000, 8M->0×00800000 16M->0×01000000, 32M->0×02000000 256M->0×10000000 64K->0×00010000 4K->0×00001000 这个是个快速记忆的方法,你可以根据地址中1的位置和其后0的个数来快速知道换算后的地址是在多少兆的地方。比如,1的后面5个0,代表1M的大小,6个0,代表16M,以此类推。 |
| ROMFS found at 0×46040000, Volume name = rom 43f291aa |
romfs,只读文件系统所在的地址为:0×46040000 (flash映射后的第3分区)。
卷名为rom。
romfs和rootfs概念上有所区别。
flash在内存中的的起始地址为0×46000000,而ROMFS在flash分区上的起始位置为0×00040000,所以ROMFS在内存地址中的位置就为0×46040000。这个细节的部分可以参考flash分区时的地方,Creating 3 MTD partitions。
romfs中包括kernel和app应用,不包括bootloader和firmware信息头。romfs只读文件系统里的内容有很多种分类方法,我们可以将kernel和app同时放里面,作为根文件系统下的一个文件,也可以在flash上另外划分区域来分别存放。
VFS虚拟文件系统交换器
在linux系统中,目前已经开发出多种文件系统,那么如何让这些文件系统能共存在一个系统中呢,从linux 2.0开始,引入了虚拟文件系统管理器 VFS的概念。
Linux 下的文件系统主要可分为三大块:
① 一是上层的文件系统的系统调用,
② 二是虚拟文件系统交换器 VFS(Virtual Filesystem Switch),
③ 三是挂载到 VFS 中的各实际文件系统,例如 ext2,jffs 等。
VFS的确切叫法是Virtual Filesystem Switch虚拟文件系统交换器,这里的VFS中的“S”是指的switch,这个需要强调一下的,它很容易被混淆成“system”,如果理解成“system”将是不正确的,请多加注意。
VFS是具体文件系统filesystem的一个管理器。
VFS是Linux内核中的一个软件层,一种软件机制,它也提供了内核中的一个抽象功能,允许不同的文件系统共存,可以称它为 Linux的文件系统管理者,与它相关的数据结构只存在于物理内存当中。所以在每次系统初始化期间,Linux 都首先要在内存当中构造一棵VFS 的目录树。VFS 中的各目录其主要用途是用来提供实际文件系统的挂载点。而rootfs将是这个目录树的根结点的(root),即”/”目录,VFS的结构就是从这个rootfs开始的。有了VFS,那么对文件的操作将使用统一的接口,将来通过文件系统调用对 VFS 发起的文件操作等指令将被 rootfs 文件系统中相应的函数接口所接管。
注意:rootfs并不是一个具体的文件系统类型,如jffs。它只是一个理论上的概念。在具体的嵌入系统实例中,可以将某种具体的文件系统设置为根文件系统rootfs,如我们可以设置romfs为根文件系统,也可以设置jffs为根文件系统。
这里的ROMFS只读文件系统只是一种具体的文件系统类型,也是在嵌入系统中经常使用到的类型。
看完了上面的内容,以后你对出现的类似“kernel Panic:VFS:Unable to mount root fs on 0:00”的含义应该已经了解了。其中“VFS:”就是虚拟文件系统管理器操作时的输出信息了。
| File linux.bin.gz found |
linux kernel内核文件名,它是在只读文件系统romfs上的一个组成部分。
| Unzipping image from 0x4639DE60 to 0×90090000, size = 1316021 |
将romfs中的linux kernel解压缩到0×90090000,之后会从这个内存地址启动内核。romfs为压缩格式文件,使用压缩的只读文件系统,是为了保持制作出来的整个系统所占用的flash空间减小。这个内核的大小为1.3M左右,这也是目前大多数嵌入系统所使用的方法。
| Inptr = 0×00000014(20)
Inflating…. |
释放,解压中。。。(变大,充气, 膨胀)
| Outcnt = 0x0030e7c8(3205064)
Final Inptr = 0x001414ad(1316013) Original CRC = 0xcbd73adb Computed CRC = 0xcbd73adb |
做释放后的CRC检查
| Boot kernel at 0×90090000 with ROMFS at 0×46040000 |
kernel已经被从romfs中释放到内存地址0×90090000处,可以跳转到此处启动kernel了,这里是指定的kernel的起始地址
| Press ‘enter’ to boot |
系统等待启动,后面将看到linux kernel的启动过程了。
用embedded-lfs构建嵌入式Linux系统
Feb 2nd
embedded-lfs代表embedded linux from scratch,也就是从头构建一个嵌入式Linux系统,不过它与标准LFS没有太大关系,只是借用了LFS这个名称而已。要说构建嵌入式Linux系统,openembedded相关项目应该是最有名的,不过它做得太复杂,如果不出问题,用起来很方便,但是一旦出点问题,就不知怎么去搞了。玩了一次,总是出问题,让我很沮丧,后来在华清远见上课时,自己整了一个embedded-lfs。embedded-lfs非常简单,当然功能也要弱一些,对于只想玩玩的初学者还是有帮助的。
o 下载embedded-lfs
svn checkout http://embedded-lfs.googlecode.com/svn/trunk/ embedded-lfs-read-only
o 创建一个名为xxxx_env.sh的脚本文件。
这里的xxxx代表板子的名称,比如为pxa300写的pxa300_env.sh内容如下:
unset TOOLCHAIN_PREFIX
export BOARD_NAME=pxa300
export TOOLCHAIN=/usr/local/arm-linux-4.1.1
. env.sh arm $1
export CFLAGS=$CFLAGS” -mcpu=xscale -fno-strict-aliasing”
这里主要是设置板子的名称和工具链的位置,和一些额外的编译选项。有三个缺省的脚本文件:
openmoko_env.sh 为openmoko手机编译
pxa300_env.sh 为marvell的pxa300编译。
x86_env.sh 编译x86的版本,可以虚拟机里测试。
o 修改软件包配置文件
配置文件里的每一项描述一个软件包,同一个软件包可以有多个项,会自动合并起来。如zlib的描述:
[zlib]
config-env = export CC=$(TARGET_CC) PREFIX=$(PREFIX)
config-cmd = configure
config-param= –prefix=$(PREFIX) –shared
url = http://www.zlib.net/zlib-1.2.3.tar.gz
config-env指定在configure之前要设置的环境变量。
config-cmd指定configure时执行的命令。
config-param指定configure时的参数。
url则是用来下载软件包的位置。
对于用标准autotool管理的软件包,更简单一些。如png的描述:
[png]
config-cmd = autoconf
url = http://nchc.dl.sourceforge.net/sourceforge/libpng/libpng-1.2.35.tar.bz2
这里只需要说明是用autoconf配置的和下载软件包的URL就行了。
另外,软件包的位置决定了软件包的编译顺序。
为了在不同平台间重用这些配置信息,可以把这些软件包的配置放在几个文件中:
1.pkg-$GDK_TARGET.ini 主要用来区分基于TinyX的GTK还是基于DirectFB的GTK。
pkg-directfb.ini 基于TinyX的GTK的缺省配置。
pkg-x11.ini 基于基于DirectFB的GTK的缺省配置。
2.pkg-$ARCH.ini 这是某类CPU特有的软件包配置。
pkg-x86.ini PC版本特有的配置
pkg-arm.ini arm版本特有的配置
3.pkg-$BOARD_NAME.ini 板子特有的配置。
4.pkg-$BOARD_NAME-$GDK_TARGET.ini 板子针对GTK不同后端的特有的配置。
o 设置编译环境变量(以pxa300为例)
如果想编译基于TinyX的GTK:
[root@localhost embedded-lfs]# . pxa300_env.sh
如果想编译基于DirectFB的GTK:
[root@localhost embedded-lfs]# . pxa300_env.sh directfb
终端会出现下列提示:
==================================================================
exported the following vars:
==================================================================
ARCH=arm
GDK_TARGET=directfb
WITH_ARCH=–with-arch=arm-linux
HOST_PARAM=–host=arm-linux
TARGET_CC=arm-linux-gcc
TARGET_PLATFORM=arm-linux-
PREFIX=/work/mine/googlecode/embedded-lfs/arm/pxa300/usr
CFLAGS=-I/work/mine/googlecode/embedded-lfs/arm/pxa300/usr/include
LDFLAGS=-L/work/mine/googlecode/embedded-lfs/arm/pxa300/usr/lib
PKG_CONFIG_PATH=/work/mine/googlecode/embedded-lfs/arm/pxa300/usr/lib/pkgconfig/
PATH=/usr/local/arm-linux-4.1.1/bin:/work/mine/googlecode/embedded-lfs/patches/bin:/usr/lib/qt-3.3/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/lib/ccache:/backup/software/jdk1.5.0_19/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/sbin:/usr/sbin::/root/bin
==================================================================
to build:
make -f Makefile.pxa300.directfb all
==================================================================
o 编译
按在设置环境变量时的提示去做就行了,如:
make -f Makefile.pxa300.directfb all
编译的结果会自动安装到ARCH/BOARD下面,如pxa300的编译结果会安装到arm/pxa300目录下。
o 问题诊断
embedded- lfs的工作原理是根据软件包配置和环境变量,生成一个Makefile文件。如前面的Makefile.pxa300.directfb ,如果编译时出现问题,可以看下这个Makefile里对应软件包的脚本是否正确就行了。比如这是freetype的编译脚本:
packages/freetype-2.3.9.tar.gz:
cd packages && wget http://ftp.twaren.net/Unix/NonGNU/freetype/freetype-2.3.9.tar.gz
freetype-2.3.9: packages/freetype-2.3.9.tar.gz
tar xf packages/freetype-2.3.9.tar.gz
freetype: freetype-2.3.9
mkdir freetype-2.3.9/$(ARCH); cd freetype-2.3.9/$(ARCH) && ../configure $(HOST_PARAM) –prefix=$(PREFIX) && make clean; make && make install
freetype_clean:
rm -rf freetype-2.3.9/$(ARCH)
freetype_source_clean:
rm -rf freetype-2.3.9
embedded-lfs的一些限制:
1.目前只支持arm和x86
2.只在Fedora上测试过,如果系统中正确安装了autotool和libtool相关工具,其它发行版本应该也不会有太多问题。
转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静
成功将2440开发板打造成下载机
Jan 27th
(这个东西不错,晚上回去尝试一下,对于一个下载狂人这个东西很符合我的胃口啊,而且板子经常闲着,这样可以把它充分利用了~)
在周未终于成功将我的友善之臂2440开发板打造成下载机,支持多种协议,完美实现下载,好了,马上开始动手吧,这个工程不大!
我的平台和环境:
开发板:友善之臂Micro2440
操作系统:Debian testing
路由器:TP-LINK TL-WR340G+ 54M无线宽带路由器
移动设备:SanDisk U盘 Cruzer Micro 4G
思路:
很简单,友善之臂2440开发板具有DM9000网卡芯片,只需用一根网线将2440和路由器连接起来,将开源的下载程序MLDonkey移植上去即可,由于开发板空间有限,所以用U盘或移动硬盘来做为下载存储空间。
MlDonkey说明:
MLDonkey是一个开源的,免费的多协议P2P服务器程序。最早只支持ED2K,后来逐步加入了overnet、kad、BT、HTTP、FTP等协议的支持,并能在Linux、Solaris、Mac OS X、Windows 以及 MorphOS下运行。 它使用OCaml语言编写,同时有些部分使用了一些C语言以及汇编语言的代码,从而保证了它的高效能。在这里,使用的是一个国外大牛编译好的可以移植到ARM上的程序,并且还有支持mipsel,PowerPC的程序下载。
步骤:
1.首先,先保证2440开发板已经整好了启动代码,内核和文件系统,把串口线连接好,并通过网线直接把开发板连接到路由器上。
2.下载编译好的MlDonkey,地址在下面,如果使用的是EABI交叉环境编译的内核,一定要下载EABI的版本(现在最新是MLdonkey 3.0.1 EABI,关于EABI:点击这里查看 ),里面有三个版本: full,normal,minimal,将full版本拷贝到U盘,几个版本区别为:
full
Enabled networks:ed2k-bittorrent-directconnect-FileTP
Library disabled:none – SUI enabled
normal
Enabled networks:ed2k-bittorrent-FileTP
Library disabled:Libgd,libmagic – SUI enabled
minimal
Enabled networks:ed2k
Library disabled:Libgd,libmagic – SUI disabled
3.打开串口终端,我使用的是Linux下的minicom,windows下的超级终端也可以,启动开发板,修改板子IP地址和你的电脑为同一个网段,网关同样为路由器所设置的网关,我设为:192.168.0.10,之后可以ping一下测试是否已通;
4.插上U盘(或其他移动设备),Micro 2440默认挂载目录为/udisk,mldonkey运行时默认目录是~/.mldonkey, 由于开发板上空间有限,使用默认目录会出现磁盘不足的提示,所以要用ln命令将~/.mldonkey链接到/udisk,依次执行下面的命令:[root@FriendlyARM /]# cd /
[root@FriendlyARM /]# mkdir .mldonkey
[root@FriendlyARM /]# ln -s /udisk/.mldonkey /.mldonkey
5.将U盘中的full整个文件夹拷贝到开发板上的一个目录下,然后进入,执行.mlnet,这就是mldonkey的应用程序文件,看到提示说core started,就说明mldonkey运行了,如果提示其他,自己查看一下,一般是磁盘空间不足的问题,再检查一下上一步ln操作是否成功将下载目录移动到U盘上;
6.在开发板上打开浏览器(注意先在开发板上看一下):http://127.0.0.1:4080,就可以运行起web管理界面了,但是在电脑上输入板子的地址和端口:http://192.167.0.10:4080会提示403 Forbidden,这是因为远程登录权限的问题,用vi打开/.mldonkey目录下的downloads.ini文件,改成如下,允许整个网段远程访问即可。
allowed_ips = [
"127.0.0.1";"192.168.0.1-192.168.0.255";]
再次在浏览器打开:http://192.167.0.10:4080,OK,出现mldonkey的web管理界面了。
7.不要激动,接下来的工作就是找资源了,电驴和BT等协议都支持,更美的是还可以下载一个图形管理界面的工具sancho,并且支持多平台管理,关于MLDonkey和sancho的用法自己去Google一下,你肯定知道;
8.到这里,下载机已经制作完成,你完全设置好下载后关闭电脑去睡大觉了,功耗低,省电费,当然你还可以做一些更加完美的工作,写个启动脚本把需要的工作让他随系统自动启动,或者做个图标在开发板上等等,Oh,这么好玩的玩意!
相关网址:
1.参考文章:http://blog.chinaunix.net/u3/93290/showart_2068553.html
2.MLDonkey 的SF主页:http://mldonkey.sourceforge.net/
3.编译好的MLDonkey下载和讨论:http://mldonkey.sourceforge.net/forums/viewtopic.php?p=26582
4.sancho的SF主页:http://sancho-gui.sourceforge.net/
5.友善之臂官方网站:http://www.arm9.net/
声明:本文来自“阿吴网志”博客,转载请务必注明本文链接地址:http://www.awuit.com/successfully-converted-2440-development-board-to-download-machine/
C语言在arm应用中的具体操作
Jan 25th
0×20其实与(1<<5)是一个数,因为 1<<5表示1向左移5位,即为100000,这个数代表16进制的20.
所以在对arm芯片的引脚赋值或者输入输出操作时,一般用这种方式
GPGDAT &= ~(1<<5);
GPBCON = (1<<(5*2)); //其实就是指定GPB口的第5位,由于每个位都是由2个字节操作,所以才*2.
ADC0CN &= ~0×20是什么意思?
首先ADC0CN &= ~0×20是一个复合赋值表达式而不是语句。ADC0CN &= ~0×20;才是语句。
这个表达式等价于ADC0CN = ADC0CN & ~0×20。其中&是位与运算,~是按位取反运算。其中~的优先级最高,~0×20的结果是0xffffffdf,也就是二进制11111111111111111111111111011111(作为对比,0×20等于0×00000020,二进制表示为00000000000000000000000000100000)。&运算的结果是当且仅当两个运算数的对应二进制位为1时才为1,因此~0×20参与运算后只有~0×20中为0的这一个二进制位的对应位置设置为0,其它的合原来的ADC0CN一致。也就是ADC0CN & ~0×20的结果是ADC0CN的第5个二进制位(最低位为第0位)修改为0后得到的值。最后再把这个值赋给原来的ADC0CN。所以整个ADC0CN &= ~0×20的结果是把ADC0CN的第5个二进制位修改为0。
嵌入式经典面试题
Jan 22nd
C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这是个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不是在嵌入式系统上。如果上述任何问题的答案是”是”的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮助。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。
预处理器(Preprocessor)
1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
2 . 写一个”标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
这个测试是为下面的目的而设的:
1) 标识#define在宏中应用的基本知识。这是很重要的。因为在 嵌入(inline)操作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3) 懂得在宏中小心地把参数用括号括起来
4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
3. 预处理器标识#error的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:”我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
…
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明(Data declarations)
5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。
Const
7.关键字const有什么含意?
我只要一听到被面试者说:”const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着”只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile
8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0×1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断(Interrupts)
11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(“\nArea = %f”, area);
return area;
}
这个函数有太多的错误了,以至让人不知从何说起了:
1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。
代码例子(Code examples)
12 . 下面的代码输出是什么,为什么?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts(“> 6″) : puts(“<= 6″);
}
这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 “>6″。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。
13. 评价下面的代码片断:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1”s complement of zero */
对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…
动态内存分配(Dynamic memory allocation)
14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?
这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:
下面的代码片段的输出是什么,为什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts(“Got a null pointer”);
else
puts(“Got a valid pointer”);
这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是”Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
Typedef
15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一个扩展为
struct s * p1, p2;
.
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。
晦涩的语法
16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。
作者介绍:
Nigel Jones 是一个顾问,现在住在Maryland,当他不在水下时,你能在多个范围的嵌入项目中找到他。 他很高兴能收到读者的来信,他的email地址是: NAJones@compuserve.com
参考文献
1) Jones, Nigel, “In Praise of the #error directive,” Embedded Systems Programming, September 1999, p. 114.
2) Jones, Nigel, ” Efficient C Code for Eight-bit MCUs ,” Embedded Systems Programming, November 1998, p. 66.
ARM体系的各种异常的分析
Jan 20th
1.复位异常
(1)当内核的nRESET信号被拉低时,ARM处理器放弃正在执行的指令,当nRESET信号再次变高时,ARM处理器进行复位操作;
(2)系统复位后,进入管理模式对系统进行初始化,复位后,只有PC(0×00000000)和CPSR(nzcvqIFt_SVC)的值是固定的,另外寄存器的值是随机的。
2.IRQ异常
(1)当CPSR中的相应的中断屏蔽被清除时,内核的nIRQ信号被拉低时可产生IRQ异常;
(2)由于ARM处理器的三级流水线结构,当异常发生时,PC的值等于当前执行指令的地址+8(即正确的中断返回地址+4),因此R14保存的值是 中断返回地址+4 ,所以当异常要返回时须执行以下指令:
SUBS PC,R14_irq,#4 ;PC=R14 – 4
注意:在SUB指令尾部有个S,并且PC是目标寄存器,所以程序返回时CPSR将自动从SPSR寄存器中恢复;
(3)将用户模式下的CPSR保存到SPSR_irq中;
(4)设置PC为IRQ异常处理程序的中断入口向量地址,在IRQ模式下该向量地址为0×00000018。
3.FIR异常
(1)当CPSR中的相应F位被清零时,内核的nFIR信号被拉低时可产生FIR异常,FIQ异常是优先级最高的中断;
(2)FIQ异常的进入和退出与IRQ异常类似;
(3)快速中断模式有8个专用的寄存器,可用来满足寄存器保护的需要,因此从其他模式进入FIQ模式时这些寄存器不用压栈了,提高程序运行的速度,且在中断入口地址的安排上,FIQ处于所有异常入口的最后,这是为了让用户可以从FIQ异常入口处(0x1c)就开始安排中断服务程序,而不需要再次跳转。
4.未定义指令异常
(1)当ARM在对一条未定义指令进行译码时,发现这是一条自己和系统内任何协处理器都无法执行的指令时,就会发生未定义指令异常;
(2)由于是在对未定义指令译码时发生异常,所以PC的值等于未定义指令的地址+4(即刚好为中断返回地址),因此R14保存的值是 中断返回地址 ,所以当异常要返回时可执行以下指令:
MOVS PC,R14_und
5.中止异常
中止表示当前存储器的访问不能完成,是由外部的ABOUT输入信号引起的异常,分为两类:
(1) 预取指中止:由程序存储器引起的中止异常;
(2) 数据中止:由数据存储器引起的中止异常;
5.1预取指中止异常
当程序发生预取指中止时,ARM内核将预取的指令标记为无效,但在指令到达流水线的执行阶段时才进入异常,因此当前PC的值为当前执行指令的地址+8(即正确的中断返回地址+4),因此R14保存的值是 中断返回地址+4 ,所以当修复了产生中止的原因后,不管在什么操作状态,处理器都会执行以下指令:
SUBS PC,R14_abt,#4 ;PC=R14 – 4
5.2数据中止异常
当发生数据中止异常时,异常会在“导致异常的指令”执行后的下一条指令时才发生,因此当前PC的值为“导致异常的指令”执行后的下一条指令的地址+8(即正确的中断返回地址+8),因此R14保存的值是 中断返回地址+8,所以当修复了产生中止的原因后,不管在什么操作状态,处理器都会执行以下指令:
SUBS PC,R14_abt,#8 ;PC=R14 – 8
注意:LPC2000系列ARM是基于ARM7TDMI内核的,不具有MMU,所以不应该发生中止异常,初学者时常会发生中止异常,大多数是因为编写的程序的问题。
6.SWI软件中断异常
(1)所有的任务都是运行在用户模式下的,因此任务只能读CPSR而不能写SPSR。任务切换到特权模式下唯一的途径就是使用一个SWI指令调用,SWI指令强迫处理器从用户模式切换到SVC管理模式,并且IRQ自动关闭,所以软件中断方式常被用于系统调用。
(2)系统调用的具体过程还是看有关uc/os-II等操作系统书,那里比较详细。
(3)SWI处理程序通过执行下面的指令返回:
MOVS PC,R14_svc
具体为什么偏移量为0,我现在也还没有搞懂,请看到的大虾多多指点,留个言,谢谢了!!!
经高手指点后明白了原来这么多异常的返回地址问题只要一句话:除了数据中止以外,所有异常发生时R14保存的值都是跳转时的PC-4,只是软件原因引起的异常时执行时(PC为该指令地址+8)就发生异常跳转了,而硬件引起的异常为了保证程序安全必须等到当前指令完成后(执行目标已经指向下一个指令,即PC为该指令地址+12)才会发生跳转。
from:21ic.com




最新评论