读《潜入式设备上的Linux系统开发》

来源:互联网 发布:gbgb888域名永久获取 编辑:程序博客网 时间:2024/04/30 01:05

读《潜入式设备上的Linux系统开发》

摘要:《嵌入式设备上的 Linux 系统开发》由Anand K Santhanam, Vishal Kulkarni,写于2002 3 01 日。文中的内容主要包括引导装载程序,设置工具链,设备驱动程序,潜入式设备的文件系统以及图形用户界面(GUI)选项。在其中,穿插讲述了一些潜入式设备的背景知识,比如一些用于潜入式场合的Linux系统。下面结合原文,以及参考Linux 2.4.20源码,谈谈我的体会。下面以黑体表示的为引用原文的内容。这里主要介绍一些背景知识以及嵌入式Linux的启动过程.本文也参考了<<构建嵌入式Linux系统>>的部分

 

1.背景知识

嵌入式 Linux 开发大致涉及三个层次:引导装载程序、Linux 内核和图形用户界面(或称 GUI)。

而在实际中,在Linux内核以后,我们也会处理嵌入式设备上的文件系统。

11引导装载程序

引导装载程序通常是在任何硬件上执行的第一段代码。这类似于普通台式机上的BIOSGrub。在台式机这样的常规系统中,通常将引导装载程序装入主引导记录(Master Boot Record(MBR))中,或者装入 Linux 驻留的磁盘的第一个扇区中。通常,在台式机或其它系统上,BIOS 将控制移交给引导装载程序。

在嵌入式设备中,一般有两种方法: 专用软件和微小的引导代码来实现将已经编译可以运行的Bootloader下载到目标板上。可以用JFlash-linux来实现将bootloader下载到目标板的闪存中。某些种类的嵌入式设备具有微小的引导代码根据几个字节的指令它将初始化一些 DRAM 设置并启用目标上的一个串行(或者 USB,或者以太网)端口与主机程序通信。然后,主机程序或装入程序可以使用这个连接将引导装载程序传送到目标上,并将它写入闪存。

引导装载程序一般能够完成下面的功能:

初始化 CPU 速度
初始化内存,包括启用内存库、初始化内存配置寄存器等
初始化串行端口(如果在目标上有的话)
启用指令/数据高速缓存
设置堆栈指针
设置参数区域并构造参数结构和标记(这是重要的一步,因为内核在标识根设备、页面大小、内存大小以及更多内容时要使用引导参数)
执行 POST(加电自检)来标识存在的设备并报告任何问题
为电源管理提供挂起/恢复支持
跳转到内核的开始

        嵌入式设备上一般可用的BootloaderCompaqbootldr(主要用于Compaq iPAQ的多功能加载程序),blob(来自LART硬件计划的加载程序),UBoot(以PPCBootARM-Boot为基础的通用加载程序),RebBoot(以eCos为基础的加载程序),sh-bootLinuxSH计划的主要加载程序)。

 

12设置工具链

         设置工具链在主机机器上创建一个用于编译将在目标上运行的内核和应用程序的构建环境。构建工具链建立了一个交叉编译器环境。本地编译器编译与本机同类的处理器的指令。交叉编译器运行在某一种处理器上,却可以编译另一种处理器的指令。

工具链由一套用于编译、汇编和链接内核及应用程序的组件组成。 这些组件包括:
• Binutils —
用于操作二进制文件的实用程序集合。它们包括诸如 arasobjdumpobjcopy 这样的实用程序。
• Gcc — GNU C
编译器。
• Glibc —
所有用户应用程序都将链接到的 C 库。避免使用任何 C 库函数的内核和其它应用程序可以在没有该库的情况下进行编译

13内核布局

         内核布局分为特定于体系结构的部分和与体系结构无关的部分。内核中特定于体系结构的部分首先执行,设置硬件寄存器、配置内存映射、执行特定于体系结构的初始化,然后将控制转给内核中与体系结构无关的部分。系统的其余部分在这第二个阶段期间进行初始化。内核树下的目录 arch/ 由不同的子目录组成,每个子目录用于一个不同的体系结构(MIPSARMi386SPARCPPC 等)。每一个这样的子目录都包含 kernel/ mm/ 子目录,它们包含特定于体系结构的代码来完成象初始化内存、设置 IRQ、启用高速缓存、设置内核页面表等操作。一旦装入内核并给予其控制,就首先调用这些函数,然后初始化系统的其余部分。
        
根据可用的系统资源和引导装载程序的功能,内核可以编译成 vmlinuxImage zImagevmlinux zImage 之间的主要区别在于 vmlinux 是实际的(未压缩的)可执行文件,而 zImage 是或多或少包含相同信息的自解压压缩文件。

         内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init 数据、bass 等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。

         内核链接脚本一般位于arch/<target>/ 目录中,名字为vmlinux.lds。下面的程序清单为linux 2.4.20(/usr/src/linux-2.4/arch/i386)vmlinux.lds部分。

         清单1lds文件(部分)

                   OUTPUT_FORMAT(elf32-i386,elf32-i386,elf32-i386)

                   OUTPUT_ARCH(i386)

                   ENTRY(_start)

                   SECTIONS

                   {

                            . = 0XC0000000 + 0X100000;

                            ._text = . ;                                        //Text and readonly data

                            .text : {

                                     *(.text)

                                     …….

                                     } = 0x9090

                            _etext = . ;                                       //End of Section

 

                            .rodata : { *(.rodata) *(.rodata.*)}

                            …….

                            . = ALIGN(16);               //Exception table

                            __start___ex_table = . ;

                            __ex_table : { *(__ex_table) }

                            __stop___ex_table = . ;

                            …….

                .data : {                                           //Data

*(.data)

CONSTRUCTORS

}

                            _edata = . ;

                            __bss_start = .;            //BSS

                            .bss : {

*(.bss)

}

                           __end = .;

                            ……

}

         对于Section中的第一行. = 0XC0000000 + 0X100000;表示的LMA地址。LMA 是装入模块地址;它表示将要装入内核的目标虚拟内存中的地址。它也可以用这种形式. = TEXTADDR;这里的TEXTADDR就需要bootloader传递。

14 bootloader到内核的参数传递

         有两种方法实现参数的传递,parameter_structure 和标记列表。但推荐使用后者。内核通过 <ATAG_TAGNAME> 头来标识每个标记

清单 2. 样本标记格式。

#define <ATAG_TAGNAME> <Some Magic number>

struct <tag_tagname> {
u32 <tag_param>;
u32 <tag_param>;
};

/* Example tag for passing memory information */

#define ATAG_MEM 0x54410002 /* Magic number */

struct tag_mem32 {
u32 size; /* size of memory */
u32 start; /* physical start address of memory*/
};

1.5设备驱动程序

         内核通过所有设备各自的设备驱动程序来控制它们,包括 GUI 用户应用程序也通过访问这些驱动程序来访问设备。

         文中介绍了使系统屏幕显示内容的帧缓冲区驱动程序,输入设备驱动程序,闪存 MTD 驱动程序。这里重点说下MTD驱动程序。

         Linux术语中,Memory Technology Devie涵盖了所有存储设备。为了尽可能避免因为不同的技术使用不同的工具,以及为不同的技术提供共同的能力,Linux内核纳入了MTD子系统。它提供了一致和统一的接口,让底层的MTD芯片驱动程序无缝地与称为“用户模块”的教高层接口组合在一起,如图1 。“MTD用户模块”指的示内核中的软件组件,它们会借着提供可识别的接口和抽象层,让内核的较高层或者用户空间能够存取底层MTD芯片驱动程序。

         有两个流行的用户模块可启用对闪存的访问:MTD_CHAR MTD_BLOCK
MTD_CHAR 提供对闪存的原始字符访问,而 MTD_BLOCK 将闪存设计为可以在上面创建文件系统的常规块设备(象 IDE 磁盘)。与 MTD_CHAR 关联的设备是 /dev/mtd0mtd1mtd2(等等),而与 MTD_BLOCK 关联的设备是 /dev/mtdblock0mtdblock1(等等)。由于 MTD_BLOCK 设备提供象块设备那样的模拟,通常更可取的是在这个模拟基础上创建象 FTL JFFS2 那样的文件系统。

         与磁盘或者DOC设备不同的是,一般不能使用fdisk或者pdisk之类的工具对MTD设备进行分区,

MTD子系统 

1:MTD子系统

 

因为分区信息通常无法保存在MTD设备上。取而代之的是,设备的分区信息会硬编码在mapping驱动程序里面,并且会在设备初始化期间向MTD子系统注册。而实际的设备根本不会包含任何的分区信息。因此必须编辑mapping驱动程序的C程序源码才能修改其分区。而mapping驱动程序的一般形式为:

 清单3:MTD分区信息

struct mtd_partition sample_partition = {
{
/* First partition */
name : bootloader, /* Bootloader section */
size : 0x00010000, /* Size */
offset : 0, /* Offset from start of flash- location 0x0*/
mask_flags : MTD_WRITEABLE /* This partition is not writable */
},
{ /* Second partition */
name : Kernel, /* Kernel section */
size : 0x00100000, /* Size */
offset : MTDPART_OFS_APPEND, /* Append after bootloader section */
mask_flags : MTD_WRITEABLE /* This partition is not writable */
},
{ /* Third partition */
name : JFFS2, /* JFFS2 filesystem */
size : MTDPART_SIZ_FULL, /* Occupy rest of flash */
offset : MTDPART_OFS_APPEND /* Append after kernel section */
}
}

16 嵌入式设备的文件系统

         文中介绍了一些文件系统,如ext2fsjffs2tmpfs,以及它们的比较和生成方法。

17 GUI

     文中介绍了MicrowindowsNano-XQt/Embedded以及它们的比较。

18带有引导装载程序、参数结构、内核和文件系统的系统典型内存布局可能如下所示:(引用)

清单 4. 典型内存布局
/* Top Of Memory */

Bootloader
Parameter Area
Kernel
Filesystem

/* End Of Memory */

 

2.内核启动过程

         嵌入式系统从上电开始,从芯片厂商指定的地址处开始执行命令(ARM的是从位置0x0开始)。一般这个地址存放的都是bootloader。在bootloader运行后,系统硬件的检查,初始化,一些寄存器的设置完成,并把后阶段内核启动需要用到的信息让到标记列表中,以待内核调用。然后,Bootloader准备接收来自主机的内核和文件系统。一旦装入了内核,引导装载程序就将控制转给内核。转向的地址就是链接脚本文件中指定的LMA

         内核中特定于体系结构的部分首先执行,设置硬件寄存器、配置内存映射、执行特定于体系结构的初始化,然后将控制转给内核中与体系结构无关的部分。系统的其余部分在这第二个阶段期间进行初始化。在这个阶段中,系统主要调用函数/init/main.c中的start_kernelstart_kernel 调用 setup_arch 作为执行的第一步,在其中完成特定于体系结构的设置。这包括初始化硬件寄存器、标识根设备和系统中可用的 DRAM 和闪存的数量、指定系统中可用页面的数目、文件系统大小等等。所有这些信息都以参数形式从引导装载程序传递到内核。setup_arch 还需要对闪存存储库、系统寄存器和其它特定设备执行内存映射。一旦完成了特定于体系结构的设置,控制就返回到初始化系统其余部分的 start_kernel 函数。在这里,start_kernel函数完成其余的初始化。这些附加的初始化任务包含:
设置陷阱
初始化中断
初始化计时器
初始化控制台
调用 mem_init,它计算各种区域、高内存区等内的页面数量
初始化 slab 分配器并为 VFS、缓冲区高速缓存等创建 slab 高速缓存
建立各种文件系统,如 procext2 JFFS2
创建 kernel_thread,它执行文件系统中的 init 命令并显示 lign 提示符。 如果在 /bin/sbin /etc 中没有 init 程序,那么内核将执行文件系统的 /bin 中的 shell