uCLinux开发介绍严永红Linux是当前一种非常受欢迎的操作系统,它与UNIX系统兼容,并开放源代码。它包含所有现代操作系统所具有的一切特性,包括多任务,虚拟内存,代码共享,按需载入,内存管理,以及TCP/IP网络。并且,它遵循POSIX标准,只要是遵循POSIXAPI的应用程序很容易被移植。目前,随着嵌入式系统的蓬勃发展。Linux也已对嵌入式系统的开发产生具大影响。大多数流行的CPU都被移植上去,ARM,PowerPC,MIPS,68K,SPARC,Alpha,SH等等.这些CPU都含有一种叫做内存管理单元(MMU)的硬件,来支持标准Linux所需要的虚拟内存。但在嵌入式世界里,还有很多CPU是没有MMU的,象ARM7、68328等等。uClinux正是为了解决这种没有MMU的CPU而产生的。在uCLinux这个英文单词中,u表示Micro,小的意思,C表示Control,控制的意思,连起来就是Micro-Control-Linux,“运行在微控制器上的Linux.”针对这种没有MMU的CPU架构,uCLinux采用了一种平板式(Flat)的内存模型来去除对MMU的依赖,并且改变了用户程序的加载方式,开发了运用于uCLinux的C函数库--uCLibc.由于这些变化,一般的Linux开发工具(例如GDB)在开发uCLinux时会碰到一些困难,包括内核的移植,驱动程序及应用程序的调试。针对这样状况。HitoolSystem公司开发了HitoolforuClinux开发套件,来帮助用户开发基于uClinux的系统。HitoolforuClinux与其它的Linux开发工具相比,有几个优点:A.整个开发过程只在Windows环境下完成,包括内核的配置、编译,应用程序的编译,文件系统的生成,内核的调试,用户程序的调试。B.可以采用多种调试方式,既可以采用JTAG方式来调试,也可通过网口用Hitool自己的监控程序(MDB)来调试。通过JTAG方式,调试可以调试内核和驱动程序,也同时可以调应用程序.一般的开发工具做不到这一点。对于那些只要调试应用程序的客户,就可以采用MDB的方式.采用这种方式,你不但不需要仿真器,而且在调试一个应用时,不会影响别的应用的运行。C.提供了一个内核的追踪工具(Trace)来帮助用户分析整个系统。在本章中,我们将结合这个工具来对uClinux做一些介绍。(在现在HitoolforuClinux套件中,提供对两种ARM-7评估板的支持,一个是Micetek的EV4.0,采用Atmel40800CPU).另一种是Micetek的EV4510b,采用Samsung的4510bCPU。一uClinux简介目前,uClinux往往基于两个linux内核版本,2.0.38是一个比较成熟的版本,2.4.x是最新的版本.Hitool套件同时提供了对他们的支持.一般uClinux的内核大小在500k左右,如果加上一些基本的应用,也就在900k左右.非常适合于嵌入式系统.uClinux架构如图1所示,一些重要的模块在下面描述:图1:uClinux架构1.(启动过程)BootstrapBootstrap负责用来起动Linux内核,包括chipSelector初始化,系统堆栈的初始化,把压缩的Linux映像从Flash中解压到RAM中,并把控制权交给内核的初始化例程。这部分工作是与你的硬件高度相关的,所以这部分的代码要尽量精简。2.内核初始化(KernelInitialization)内核初始化的入口地址是:start_kernel(在init/main.c中),它初始化内核的其它部分,包括异常(trap)、中断(IRQ)、内存页(Page)、调度(Scheduling)、驱动程序等等。并启动“init”进程进入多任务环境。3.系统调用处理/异常处理:当“init”程序运行后,内核对整个系统的运行不再进行直接控制,而是通过系统调用给应用程序提供服务和响应外部及内部的异步事件,例如:程序错误,硬件中断等。用户程序如果想得到系统资源,必须通过系统调用.当用户进程发生中断后,内核获得控制权,取得系统调用的参数,并调用相应的处理程序,而用户一直被挂起,直到内核完成处理并返回.在ARM中,系统调用采用swi指令所产生的软件异常来实现,如例所示:swi0x90004(其中0x900004表示这个系统调用为sys_write)。00002550write:2550:ef900004swi0x009000042554:e3700a01cmnr0,#4096;0x10002558:2afff6b0bcs20\__syscall_error\255c:e1a0f00emovpc,lr4.驱动程序(DeviceDriver):驱动程序是整个Linux内核的主要组成部分,它们控制着操作系统和外部设备的交互.例如,串口驱动程序(arch/armnommu/mach-atmel/…….)处理由外部UART发生中断.Linux的驱动程序是可选的,但是典型的系统应该包括一个控制台(Console),一个通用串口驱动程序,一个块设备驱动程序(用来存放根文件系统)。当Linux内核起动的时候,需要一个输出调试信息的设备。这个设备往往通过串口来实现。这个调试终端可以通过register-console这个函数来创建.而所有的调试信息都通过Printk例程通过这个调试终端来输出.5.文件系统(FileSystem)支持多种文件系统是Linux一个重要的特性,uClinux同样把这一特性带进了嵌入式系统中,并针对嵌入式系统作了一些取舍。在HitoolforuClinux包括了ROMFS,Ext2FS,RAMFS,NFS.其中,ROMFS是最简单的只读文件系统,所占用的空间最少,我们用它来做根文件系统(rootfilesystem).根文件系统里存放linux启动时要用到的设备文件,配置文件,和应用程序,例如:/dev/tty0,/etc/rc,/bin/init,/bin/sh,等等。6.内存管理uClinux不能使用处理器的虚拟内存管理技术但仍然采用存储器的分页管理,系统在启动时把实际存储器进行分页。在加载应用程序时程序分页加载。但是由于没有MMU管理,所以实际上uClinux采用实存储器管理策略(物理内存)。这一点影响了系统工作的很多方面。uClinux系统对于内存的访问是直接的,(它对地址的访问不需要经过MMU,而是直接送到地址线上输出),所有程序中访问的地址都是实际的物理地址。操作系统对内存空间没有保护(这实际上是很多嵌入式系统的特点),各个进程实际上共享一个运行空间(没有独立的地址转换表)。一个进程在执行前,系统必须为进程分配足够的连续地址空间,然后全部载入主存储器的连续空间中。与之相对应的是标准Linux系统在分配内存时没有必要保证实际物理存储空间是连续的,而只要保证虚存地址空间连续就可以了。另外一个方面程序加载地址与预期(ld文件中指出的)通常都不相同,这样重定位的过程就是必须的。uClinux对内存的管理减少同时就给开发人员提出了更高的要求。如果从易用性这一点来说,uClinux的内存管理是一种倒退,退回到了Dos时代。开发人员不得不参与系统的内存管理。从编译内核开始,开发人员必须告诉系统这块开发板到底拥有多少的内存(假如你欺骗了系统,那将在后面运行程序时受到惩罚),从而系统将在启动的初始化阶段对内存进行分页,并且标记已使用的和未使用的内存。系统将在运行应用时使用这些分页内存。由于应用程序加载时必须分配连续的地址空间,所以开发人员在开发应用程序时必须考虑内存的分配情况并关注应用程序需要运行空间的大小。另外由于采用实存储器管理策略,用户程序同内核以及其它用户程序在一个地址空间,程序开发时要保证不侵犯其它程序的地址空间,以使得程序不至于破坏系统的正常工作,或导致其它程序的运行异常。从内存的访问角度来看,开发人员的权利增大了(开发人员在编程时可以访问任意的地址空间),但与此同时系统的安全性也大为下降。此外,系统对多进程的管理将有很大的变化,这一点将在uClinux的多进程管理中说明。虽然uClinux的内存管理与标准Linux系统相比功能相差很多,但应该说这是嵌入式设备的选择。在嵌入式设备中,由于成本等敏感因素的影响,普遍的采用不带有MMU的处理器,这决定了系统没有足够的硬件支持实现虚拟存储管理技术。从嵌入式设备实现的功能来看,嵌入式设备通常在某一特定的环境下运行,只要实现特定的功能,其功能相对简单,内存管理的要求完全可以由开发人员考虑。7.进程管理uClinux和Linux之间最大的区别在于平面存储器模型。没有虚拟存储器可以提供非常有效的fork系统调用.由于在使用fork时,内核会将父进程拷贝一份给子进程,但是这样的做法相当浪费时间,因为大多数的情形都是程序在调用fork后就立即调用exec,这样刚拷贝来的进程区域又立即被新的数据覆盖掉。因此Linux系统提供一个系统调用vfork,vfork假定系统在调用完成vfork后会马上执行exec,因此vfork不拷贝父进程的页面,只是初始化私有的数据结构与准备足够的分页表。这样实际在vfork调用完成后父子进程事实上共享同一块存储器(在子进程调用exec或是exit之前),因此子进程可以更改父进程的数据及堆栈信息,因此vfork系统调用完成后,父进程进入睡眠,直到子进程执行exec。并且在这段时间内,子进程不能更改数据段和堆栈的内容.当子进程执行exec时,由于exec要使用被执行程序的数据,代码覆盖子进程的存储区域,这样将产生写保护错误(do_wp_page)(这个时候子进程写的实际上是父进程的存储区域),这个错误导致内核为子进程重新分配存储空间。当子进程正确开始执行后,将唤醒父进程,使得父进程继续往后执行。所以,在uClinux中,想让父子进程同时运行同一个程序是不可以的.8.运行时间库(CRuntimeLib)及应用程序运行时间库提供了用户程序和内核程序的接口。尽管许多人认为他们的系统是Linux系统(指运行内核),但大部分系统的性能不是由内核决定的,而是由C时间库决定的。例如,运行时间库将一条ptrintf语句转换为一个系统调用(sys_write),将输出发送到标准输出上。系统调用库也必须根据系统的要求进行裁减。uClibc就是经过裁减后的适用于嵌入式设备的C-运行时间库。它由GNUglibc库移植而来。除了去掉了大部分不适合嵌入式系统的代码以外,还对应用程序的入口代码,系统调用的API,输入输出过程(I/Oroutines)等进行了修改。并增加了对平面存储器结构的支持。这部分是uClinux的难点.程序的入口代码用户自己写程序一般从main开始,其实程序在运行时真正的进入点是C运行库的一部分。这部分代码执行初始静态数据,例如错误号(errno)以及环境指示符(environ),并且将参数argc和argv传输到main。参数和环境一般根据宏程序start_thread(定义在include/proc/processer.h)的调用约定,放在寄存器或者堆栈上。execve系统调用利用start_thread建立一个虚拟环境,程序的入口代码因此必须实现把start_thread所建立的环境恢复出来,并调用__uClibc_main。在__uClibc_main中再调用用户程序的main。.text.global_start.global__uClibc_main.type_start,%function.type__uClibc_main,%function.text_start:/*cleartheframepointer*/movfp,#0/*pullargc,argvandenvpoffthestack*/ldrr0,[sp,#0]ldrr1,[sp,#4]ldrr2,[sp,#8]/*Ok,nowrunuClibc'smain()--shouldn'tre