世纪电源网社区logo
社区
Datasheet
标题
返回顶部
讨论

嵌入式设备上的 Linux 系统开发-1

[复制链接]
查看: 1015 |回复: 0
1
my770809
  • 积分:4421
  • |
  • 主题:16
  • |
  • 帖子:95
积分:4421
LV8
副总工程师
  • 2021-8-10 09:18:29
嵌入式 Linux 开发大致涉及三个层次:引导装载程序、Linux 内核和图形用户界面(或称 GUI)。在本文中,我们将集中讨论涉及这三层的一些基本概念;深入了解引导装载程序、内核和文件系统是如何交互的;并将研究可用于文件系统、GUI 和引导装载程序的众多选项中的一部分。
引导装载程序
引导装载程序通常是在任何硬件上执行的第一段代码。在像台式机这样的常规系统中,通常将引导装载程序装入主引导记录(Master Boot Record MBR)中,或者装入 Linux 驻留的磁盘的第一个扇区中。通常,在台式机或其它系统上,BIOS 将控制移交给引导装载程序。这就提出了一个有趣的问题:谁将引导装载程序装入(在大多数情况中)没有 BIOS 的嵌入式设备上呢?
解决这个问题有两种常规技术:专用软件和微小的引导代码(tiny bootcode)。
专用软件可以直接与远程系统上的闪存设备进行交互并将引导装载程序安装在闪存的给定位置中。 闪存设备是与存储设备功能类似的特殊芯片,而且它们能持久存储信息―即,在重新引导时不会擦除其内容。
这个软件使用目标(在嵌入式开发中,嵌入式设备通常被称为 目标)上的 JTAG 端口,它是用于执行外部输入(通常来自主机机器)的指令的接口。JFlash-linux 是一种用于直接写闪存的流行工具。它支持为数众多的闪存芯片;它在主机机器(通常是 i386 机器 ― 本文中我们把一台i386 机器称为 主机)上执行并通过 JTAG 接口使用并行端口访问目标的闪存芯片。当然,这意味着目标需要有一个并行接口使它能与主机通信。Jflash-linux 在 Linux 和 Windows 版本中都可使用,可以在命令行中用以下命令启动它:
Jflash-linux <bootloader>
某些种类的嵌入式设备具有 微小的引导代码― 根据几个字节的指令 ― 它将初始化一些 DRAM 设置并启用目标上的一个串行(或者 USB,或者以太网)端口与主机程序通信。然后,主机程序或装入程序可以使用这个连接将引导装载程序传送到目标上,并将它写入闪存。
在安装它并给予其控制后,这个引导装载程序执行下列各类功能:
初始化 CPU 速度
初始化内存,包括启用内存库、初始化内存配置寄存器等
初始化串行端口(如果在目标上有的话)
启用指令/数据高速缓存
设置堆栈指针
设置参数区域并构造参数结构和标记(这是重要的一步,因为内核在标识根设备、页面大小、内存大小以及更多内容时要使用引导参数)
执行 POST(加电自检)来标识存在的设备并报告任何问题
为电源管理提供挂起/恢复支持
跳转到内核的开始
带有引导装载程序、参数结构、内核和文件系统的系统典型内存布局可能如下所示:

清单 1. 典型内存布局
   /* Top Of Memory */
  
         
Bootloader
        Parameter Area
        Kernel
        Filesystem
  

      /* End Of Memory */
嵌入式设备上一些流行的并可免费使用的Linux 引导装载程序有 Blob、Redboot 和 Bootldr(请参阅 参考资料获得链接)。所有这些引导装载程序都用于基于 ARM 设备上的 Linux,并需要Jflash-linux 工具用于安装。
一旦将引导装载程序安装到目标的闪存中,它就会执行我们上面提到的所有初始化工作。然后,它准备接收来自主机的内核和文件系统。一旦装入了内核,引导装载程序就将控制转给内核。
设置工具链
设置工具链在主机机器上创建一个用于编译将在目标上运行的内核和应用程序的构建环境 ― 这是因为目标硬件可能没有与主机兼容的二进制执行级别。
工具链由一套用于编译、汇编和链接内核及应用程序的组件组成。 这些组件包括:
·        Binutils ― 用于操作二进制文件的实用程序集合。它们包括诸如ar 、           as 、 objdump 、 objcopy 这样的实用程序。
·        Gcc― GNU C 编译器。
·        Glibc所有用户应用程序都将链接到的C 库。避免使用任何 C 库函数的内核和其它应用程序可以在没有该库的情况下进行编译。
构建工具链建立了一个交叉编译器环境。 本地编译器编译与本机同类的处理器的指令。交叉编译器运行在某一种处理器上,却可以编译另一种处理器的指令。重头设置交叉编译器工具链可不是一项简单的任务:它包括下载源代码、修补补丁、配置、编译、设置头文件、安装以及很多很多的操作。另外,这样一个彻底的构建过程对内存和硬盘的需求是巨大的。如果没有足够的内存和硬盘空间,那么在构建阶段由于相关性、配置或头文件设置等问题会突然冒出许多问题。
因此能够从因特网上获得已预编译的二进制文件是一件好事(但不太好的一点是,目前它们大多数只限于基于 ARM 的系统,但迟早会改变的)。一些比较流行的已预编译的工具链包括那些来自Compaq(Familiar Linux )、LART(LART Linux)和 Embedian(基于 Debian 但与它无关)的工具链 ― 所有这些工具链都用于基于 ARM 的平台。
内核设置
Linux 社区正积极地为新硬件添加功能部件和支持、在内核中修正错误并且及时地进行常规改进。这导致大约每 6 个月(或 6 个月不到)就有一个稳定的 Linux 树的新发行版。不同的维护者维护针对特定体系结构的不同内核树和补丁。当为一个项目选择了一个内核时,您需要评估最新发行版的稳定性如何、它是否符合项目要求和硬件平台、从编程角度来看它的舒适程度以及其它难以确定的方面。还有一点也非常重要:找到需要应用于基本内核的所有补丁,以便为特定的体系结构调整内核。
内核布局
内核布局分为特定于体系结构的部分和与体系结构无关的部分。内核中特定于体系结构的部分首先执行,设置硬件寄存器、配置内存映射、执行特定于体系结构的初始化,然后将控制转给内核中与体系结构无关的部分。系统的其余部分在这第二个阶段期间进行初始化。内核树下的目录 arch/ 由不同的子目录组成,每个子目录用于一个不同的体系结构(MIPS、ARM、i386、SPARC、PPC 等)。每一个这样的子目录都包含 kernel/ 和 mm/ 子目录,它们包含特定于体系结构的代码来完成像初始化内存、设置 IRQ、启用高速缓存、设置内核页面表等操作。一旦装入内核并给予其控制,就首先调用这些函数,然后初始化系统的其余部分。
根据可用的系统资源和引导装载程序的功能,内核可以编译成 vmlinux、Image 或zImage。vmlinux 和 zImage 之间的主要区别在于vmlinux是实际的(未压缩的)可执行文件,而 zImage是或多或少包含相同信息的自解压压缩文件 ― 只是压缩它以处理(通常是 Intel 强制的)640 KB 引导时间的限制。有关所有这些的权威性解释,请参阅 Linux Magazine的文章“Kernel Configuration:dealing with the unexpected”(请参阅 参考资料)。
内核链接和装入
一旦为目标系统编译了内核后,通过使用引导装载程序(它已经被装入到目标的闪存中),内核就被装入到目标系统的内存(在 DRAM 中或者在闪存中)。通过使用串行、USB 或以太网端口,引导装载程序与主机通信以将内核传送到目标的闪存或 DRAM 中。在将内核完全装入目标后,引导装载程序将控制传递给装入内核的地址。
内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init 数据、bass 等等。这些对象文件都是由一个称为 链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。vmlinux.lds是存在于 arch/<target>/ 目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。典型的 vmlinux.lds 看起来象这样:

清单 2. 典型的 vmlinux.lds 文件
OUTPUT_ARCH(<arch>)        /* <arch> includes architecture type */
   ENTRY(stext)               /* stext is the kernel entry point */
   SECTIONS         /* SECTIONS command describes the layout of the output file */
   {
       .  = TEXTADDR;        
/* TEXTADDR is LMA for the kernel */
  
     .init : {              /* Init code and data*/
                _stext = .;      
/* First section is stext followed by __init data section */
                __init_begin = .;
                       *(.text.init)
                __init_end = .;
               }
       .text : {          /* Real text segment follows __init_data section */
                _text = .;
                       *(.text)
                _etext = .;       /* End of text section*/
               }
       .data :{
                _data=.;          /* Data section comes after text section */
                       *(.data)
                _edata=.;  
               }                  /* Data section ends here */
       .bss : {                   /* BSS section follows symbol table section */
                __bss_start = .;
                       *(.bss)
                _end = . ;        /* BSS section ends here */  
               }
    }
LMA 是装入模块地址;它表示将要装入内核的目标虚拟内存中的地址。 TEXTADDR 是内核的虚拟起始地址,并且在 arch/<target>/ 下的 Makefile 中指定它的值。这个地址必须与引导装载程序使用的地址相匹配。
一旦引导装载程序将内核复制到闪存或 DRAM 中,内核就被重新定位到 TEXTADDR它通常在 DRAM 中。然后,引导装载程序将控制转给这个地址,以便内核能开始执行。
参数传递和内核引导
stext 是内核入口点,这意味着在内核引导时将首先执行这一节下的代码。它通常用汇编语言编写,并且通常它在 arch/<target>/ 内核目录下。这个代码设置内核页面目录、创建身份内核映射、标识体系结构和处理器以及执行分支 start_kernel (初始化系统的主例程)。
start_kernel 调用 setup_arch 作为执行的第一步,在其中完成特定于体系结构的设置。这包括初始化硬件寄存器、标识根设备和系统中可用的 DRAM 和闪存的数量、指定系统中可用页面的数目、文件系统大小等等。所有这些信息都以参数形式从引导装载程序传递到内核。
将参数从引导装载程序传递到内核有两种方法:parameter_structure和标记列表。在这两种方法中,不赞成使用参数结构,因为它强加了限制:指定在内存中,每个参数必须位于param_struct 中的特定偏移量处。最新的内核期望参数作为标记列表的格式来传递,并将参数转化为已标记格式。param_struct 定义在 include/asm/setup.h 中。它的一些重要字段是:

清单 3. 样本参数结构
struct param_struct  {
    unsigned long page_size;     /* 0:  Size of the page  */
    unsigned long nr_pages;      /* 4:  Number of pages in the system */
    unsigned long ramdisk        /* 8:  ramdisk size */
    unsigned long rootdev;       /* 16: Number representing the root device */
    unsigned long initrd_start;  /* 64: starting address of initial ramdisk */
                                 /* This can be either in flash/dram */
    unsigned long initrd_size;   /* 68: size of initial ramdisk */
   }
请注意:这些数表示定义字段的参数结构中的偏移量。这意味着如果引导装载程序将参数结构放置在地址 0xc0000100,那么 rootdev 参数将放置在 0xc0000100 + 16,initrd_start 将放置在 0xc0000100 + 64 等等 ― 否则,内核将在解释正确的参数时遇到困难。
正如上面提到的,因为从引导装载程序到内核的参数传递会有一些约束条件,所以大多数 2.4.x 系列内核期望参数以已标记的列表格式传递。在已标记的列表中,每个标记由标识被传递参数的tag_header 以及其后的参数值组成。标记列表中标记的常规格式可以如下所示:

清单 4. 样本标记格式。内核通过 <ATAG_TAGNAME> 头来标识每个标记。
#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*/
   };


收藏收藏
热门技术、经典电源设计资源推荐

世纪电源网总部

地 址:天津市南开区黄河道大通大厦8层

电 话:400-022-5587

传 真:(022)27690960

邮 编:300110

E-mail:21dy#21dianyuan.com(#换成@)

世纪电源网分部

广 东:(0755)82437996 /(138 2356 2357)

北 京:(010)69525295 /(15901552591)

上 海:(021)24200688 /(13585599008)

香 港:HK(852)92121212

China(86)15220029145

网站简介 | 网站帮助 | 意见反馈 | 联系我们 | 广告服务 | 法律声明 | 友情链接 | 清除Cookie | 小黑屋 | 不良信息举报 | 网站举报

Copyright 2008-2024 21dianyuan.com All Rights Reserved    备案许可证号为:津ICP备10002348号-2   津公网安备 12010402000296号