本章以ARM平台为例介绍了内核移植的基本技巧,而且详尽剖析了Linux内核启动过程和各类Linux内核调试方式。通过本章学习,可以明晰内核什么代码是与平台相关的,在内核启动过程中代码的执行次序。只有把握了这种代码,在内核移植过程中才会有的放矢地去更改代码。内核的调试须要从内核源码本身、调试工具等方面做好打算。可以了解不同调试方法的特征和使用方式,依据须要选择不同的内核调试方法。所谓移植就是把程序代码从一种运行环境转移到另外一种运行环境。对于内核移植来说,主要是从一种硬件平台转移到另外一种硬件平台上运行。1.选择参考板选择参考板的原则如下:(1)参考板与开发板具有相同的处理器,起码类似的处理器;(2)参考板与开发板具有相同的外围插口电路,起码基本插口相同;(3)Linux内核早已支持参考板,起码有非官方的补丁或则BSP;(4)参考板Linux设备驱动工作正常linux内核添加函数,起码早已驱动基本插口。2.编译测试参考板的Linux内核3.剖析参考板的BSP代码对于内核移植工作来说,主要是添加开发板初始化和驱动程序的代码。1.添加开发板平台支持选项内核配置选项的“SystemType”中有处理器及开发板的支持选项。
这种与ARM平台相关的选项都是在arch/arm目录下实现的。在内核编译过程中,须要在顶楼Makefile中设置相应的体系结构和工具链。这样配置Linux内核的时侯才会调用arch/arm/Kconfig文件。2.移植开发板驱动程序不同的开发板可以使用不同的SDRAM、Flash、以太网插口芯片等。这就须要依照硬件更改或则开发驱动程序。1.获得Linux内核源码并解压缩2.更改Makefile文件ARCH$(SUBARCH)CROSS_COMPILE更改成如下两行:ARCHarmCROSS_COMPILEarm-linux-3.得到.config文件因为我们的配置和SMDK2410开发板的类似,因而可以使用它的.config文件,只须要把它的.config文件拷贝到源代码根目录下的.config文件即可:#cparch/arm/configs/s3c2410_defconfig.config4.更改NandFlash分区5.添加LCD支持6.添加网卡驱动支持7.添加YAFFS文件系统支持8.配置和编译内核makeconfig或makemenuconfig或makexconfigmake最后在arch/arm/boot目录下生成内核文件zImage。
编译U-Boot时在源代码的tools目录下会生成一个mkimage可执行文件,用这个工具可以对上面编译内核时生成的zImage进行处理,以供U-Boot启动。#./mkimage´Linux-2.6.24´uImage9.用U-Boot启动内核为何要用U-boot的mkimage工具处理内核映像zImage?由于在用bootm命令引导内核的时侯,bootm须要读取一个64字节的文件头,来获取这个内核映象所针对的CPU体系结构、OS、加载到显存中的位置、在显存中入口点的位置以及映象名等信息。这样bootm能够为OS设置好启动环境,并坠入内核映象的入口点。而mkimage就是添加这个文件头的专用工具。具体的实现请看U-boot中bootm的源码和mkimage的源码。mkimage工具的使用:参数说明:-A:指定CPU的体系结构,如:alpha、arm、x86、mips、ppc等-O:指定操作系统类型,如:linux、vxworks、psos、qnx等-T:指定映象类型,如:kernel、ramdisk、filesystem-C:指定映象压缩方法,如:none(不压缩)gzip(用gzip的压缩方法)bzip2(用bzip2的压缩方法)-a:指定映象在显存中的加载地址,映象下载到显存中时-e:指定映象运行的入口点地址-n:指定映象名-d:指定制做映象的源文件移植完成之后,就可以发布这个内核源代码了。
最常见的形式是发布内核补丁。基于一个稳定的内核版本制做补丁文件,可以便捷地保存和分发。Linux内核启动就是引导内核映像启动的过程。典型的内核映像是zImage,包含自引导程序和压缩的vmlinux这两部份。启动过程从内核映像入口开始执行,解压vmlinux而且转换到虚拟地址空间,再调用统一的内核启动函数start_kernel(),进而启动整个Linux系统。内核启动流程图start:decompress_kernel(stext:start_kernel(Execve(“/sbin/init”,argv_init,envp_init)解压缩内核关掉CACHElinux内核添加函数,并将控制权交给内核内核初始化函数初始化命令列表启动init内核线程挂接文件系统前的初始化初始化设备驱动挂接一个根文件系统执行应用程序zImage映像的入口代码是自引导程序。自引导程序包含一些初始化代码,所以它是体系结构相关的,这个目录是arch/$(ARCH)/boot。这么第一条指令所在的文件是自引导程序中的head.S。PC表针早已指向vmlinux的入口地址,次序执行内核启动程序。vmlinux开始部份也有一些汇编程序,对应的程序文件也是head.S。
经过一系列的初始化过程,打开MMU,跳转到start_kernel()函数。start_kernel函数是Linux内核通用的初始化函数。无论对于哪些体系结构的Linux,都要执行这个函数。start_kernel()是内核初始化的基本过程。start_kernel()函数负责初始化内核各子系统,最后调用rest_init(),启动一个叫作init的内核线程,继续初始化。在init内核线程中,将执行init()函数的程序。init函数负责完成挂接根文件系统、初始化设备驱动和启动用户空间的init进程等重要工作。Linux才能在显存中虚拟c盘文件系统,叫作ramdisk。倘若为内核配置了ramdisk设备和文件系统,就安装好ramdisk文件系统。之后再挂接根文件系统,不过要在初始化设备驱动程序然后执行prepare_namespace()函数。它负责为Linux系统挂接一个根文件系这儿内核子系统早已基本上初始化好了,CPU子系统早已正常工作,显存管理和进程管理早已正常运转,并且还没有使用任何设备。接出来继续初始化内核设备驱动程序,之后才会访问设备,做系统真正想要做的任务。Linux系统在挂接根文件系统以后,要执行文件系统的中的应用程序。
有些应用程序可能要完成嵌入式系统的。init进程是通过执行根文件系统中init程序启动的。对于庞大的Linux内核软件工程,单靠阅读代码查找问题早已十分困难,须要利用调试技术解决BUG。通过合适的调试手段,可以有效地查找和判定BUG的位置和缘由。调试内核很难,实际上内核不同于其他软件工程。内核有操作系统奇特的问题,比如:时间管理和条件竞争,这可以使多个线程同时在内核中执行。为此,调试BUG须要有效的调试手段。几乎没有一种调试工具或则方式才能解决全部问题。虽然在一些集成测试环境中,也要界定不同测试调试功能,比如:跟踪调试、内存泄露测试、性能测试等。把握的调试方式越多,调试BUG就越便捷。Linux有好多开放源代码的工具,每一个工具的调试功能专情,所以这种工具的实现通常也比较简单。因为内核的复杂性,无论使用哪些调试手段,都须要熟悉内核源码。只有熟悉了内核各部份的代码实现linux操作系统安装,才才能找到确切的跟踪点;只有熟悉操作系统的内核机制,能够确切地判断系统运行状态。阅读内核源代码是十分乏味的工作。最好先把握一种搜索工具,学会从源码树中搜索关内核调试方式主要有以下4类:(1)通过复印函数(2)获取内核信息(3)处理出错信息(4)内核源码调试在调试内核之前,一般须要配置内核的调试选项。
在“Kernelhacking”配置菜单下有各类调试选项。不同的调试方式,须要配置对应的选项。每一种调试选项针对不同的调试功能。但是不是所有的调试选项在所有的平台上都还能支持。嵌入式系统通常都可以通过并口与用户交互。大多数Bootloader可以向并口打印信息,但是接收命令。内核同样可以向并口复印信息。并且在内核启动过程中,不同阶段的复印函数不同。剖析这种复印函数的实现,可以更好地调试内核。6.4.1内核映像解压前的并口输出函数decompresss_kernel()函数调用了putstr()函数,直接向并口复印内核解压的信息。6.4.2内核错误报告子程序可以通过printascii子程序来向并口复印。printasciiarch/arm/kernel/debug.S文件中。6.4.3内核复印函数Linux内核标准的系统复印函数是printkprintk函数具有极好的强壮性,不受内核运行条件的限制,在系统运行期间都可以使用。Linux内核提供了一些与用户空间通信的机制,大部份驱动程序与用户空间的插口都可以作为获取内核信息的手段。另外内核也有专门的调试机制。
proc文件系统是一种伪文件系统。实际上,它并不占用储存空间,而是系统运行时在显存中构建的内核状态映射,可以瞬时地提供系统的状态信息。在用户空间可以作为文件系统挂接到/proc目录下,提供给用户访问。可以通过shell命令挂接,也可以在/etc/fstab中作出相应的设置。procproc/proc通过proc文件系统可以查看运行中的内核。查询和控制运行中的进程和系统资源等状态。这对于监控性能、查找系统信息、了解系统是怎样配置的及修改该配置十分有cat/proc/cpuinfosysfs文件系统是Linux2.6内核新降低的文件系统。它也是一种伪文件系统,是在显存中实现的文件系统。它可以把内核空间的数据、属性、链接等东西输出到用户空间。一般sysfs文件系统要挂接到/sys目录下,提供给用户空间访问。可以通过shell命令挂接,也可以在/etc/fstab中作出相应的设置。sysfssysfs/syssysfs文件系统的目录组织结构反映了内核数据结构关系。可以通过编程进行处理。ioctl是对一个文件描述符响应的系统调用,它可以实现特殊命令操作。
ioctl可以代替/proc文件系统,实现一些调试的命令。使用ioctl获取信息比/proc麻烦一些,由于通过应用程序的ioctl函数调用而且显示结果必须编撰、编译一个应用程序,而且与正在测试的模块保持一致。反过来linux系统下载,驱动程序代码比实现/proc文件相对简单一点。当系统出现错误时,内核有两个基本的错误处理机制:oops和panic。6.6.1oops信息(1)oops消息包含系统错误的详尽信息一般oops信息中包含当前进程的栈回溯和CPU寄存器的内容。(2)使用ksymoops转换oops信息(3)内核kallsyms选项支持符号信息6.6.2panic函数当系统发生严重错误的时侯,将调用panic()函数。panic()函数首先尽可能把出错信息复印下来。由于Linux内核程序是GNUGCC编译的,所以对应地使用GNUGDB调试器。Linux应用程序须要gdbserver辅助交叉调试。这么内核源代码调试时,谁来充当gdbserver的角色呢?KGDB是Linux内核调试的一种机制。它使用远程主机上的GDB调试目标板上的Linux内确切地说,KGDB是内核的功能扩充,它在内核中使用插桩(stub)的机制。
内核在启动时等待远程调试器的联接,相当于实现了gdbserver的功能。之后,远程主机的调试器GDB负责读取内核符号表和源代码,但是建立联接。接出来,就可以在内核源代码中设置断点、检查数据并进行其他操作。遵守下边的步骤来设置KGDB调试环境:(1)配置编译Linux内核映像。(2)在目标板上启动内核。(3)启动gdb,构建联接。(4)使用gdb的调试命令设置断点,跟踪调试。HOSTTARGET并口联接内核源码内核运行gdbKGDBstub下边说明一下BDI2000调试Linux内核的操作步骤:(1)主机/目标机设置(2)打算要调试的内核(3)通过BDI2000控制硬件开发板(4)设置BDI2000断点(5)下载内核(6)gdb联接BDI2000(7)设置gdb断点(8)重新控制调试过程(9)调试内核模块HOSTTARGET内核源码内核gdbBDI2000相当于gdbserverJTAG以太网