这是一系列文章,说明Linux内核如何在ARM体系结构上启动。这是第一部分。

ARM Linux引导过程:

我们将解释围绕ARM920T ARM Thumb处理器构建的AT91RM9200片上系统的启动过程。 Kwickbyte基于AT91RM9200构建了一个名为kb9202的嵌入式板。我们将以该板为例,了解Linux如何在该板上启动。

在开始阅读本文档之前,您需要阅读AT91RM9200数据手册(规格)。

您还需要阅读《 ARM体系结构参考手册》,以更好地理解引导过程。

Linux引导过程中的组件:

Linux引导过程涉及以下组件。

引导程序

内核映像

根文件系统

在我们了解上述组件如何工作之前,以下是用于arm体系结构的Linux Kernel引导过程的调用流程。这给了整个Linux启动过程的全景图。我们使用U-boot引导程序。

ARM Linux引导过程:大图

U型靴:

_start(cpu / arm920t / start.S)

start_code(cpu / arm920t / start.S)

start_armboot(lib_arm / board.c)

board_init(board / kb9202 / kb9202.c)

timer_init(cpu / arm920t / at91 / timer.c)

serial_init(驱动程序/串行/at91rm9200_usart.c)

main_loop(lib_arm / board.c)

现在,u-boot已启动并正在运行,并且处于u-boot提示符下并准备接受命令。假定内核映像已加载到RAM中并发出了bootm命令。

do_bootm(common / cmd_bootm.c)

bootm_start(common / cmd_bootm.c)

bootm_load_os(common / cmd_bootm.c)

do_bootm_linux(lib_arm / bootm.c)

stext(linux / arch / arm / kernel / head.S)

控制权交给了Linux。

Linux内核:

stext(arch / arm / kernel / head.S:78)

__lookup_processor_type(arch / arm / kernel / head-common.S:160)

__lookup_machine_type(arch / arm / kernel / head-common.S:211)

__create_page_tables(arch / arm / kernel / head.S:219)

__arm920_setup(arch / arm / mm / proc-arm920.S:389)

__enable_mmu(arch / arm / kernel / head.S:160)

__turn_mmu_on(足弓/手臂/内核/头部.S:205)

__switch_data(arch / arm / kernel / head-common.S:20)

start_kernel(init / main.c:529)

start_kernel(init / main.c:529)

tick_init(内核/时间/tick-common.c:413)

setup_arch(arch / arm / kernel / setup.c:666)

setup_machine(arch / arm / kernel / setup.c:369)

lookup_machine_type()

setup_command_line(init / main.c:408)

build_all_zonelists(mm / page_alloc.c:3031)

parse_args(内核/参数.c:129)

mm_init(init / main.c:516)

mem_init(arch / arm / mm / init.c:528)

kmem_cache_init(mm / slab.c,mm / slob.c,mm / slub.c)

sched_init(内核/sched.c)

init_IRQ(arch / arm / kernel / irq.c)

init_timers(内核/timer.c:1713)

hrtimers_init(内核/hrtimer.c:1741)

softirq_init(内核/softirq.c:674)

console_init(驱动程序/char/tty_io.c:3084)

vfs_caches_init(fs / dcache.c:2352)

mnt_init(fs / namespace.c:2308)

init_rootfs()

init_mount_tree(fs / namespace.c:2285)

do_kern_mount(fs / namespace.c:1053)

set_fs_pwd(fs / fs_struct.c:29)

set_fs_root(fs / fs_struct.c:12)

bdev_cache_init(fs / block_dev.c:465)

chrdev_init(fs / char_dev.c:566)

signals_init(内核/signal.c:2737)

rest_init(init / main.c:425)

kernel_thread(431,arch / arm / kernel / process.c:388)

kernel_thread()创建一个内核线程,并将控制权交给kernel_init()。

kernel_init(431,init / main.c:856)

do_basic_setup(888,init / main.c:787)

init_workqueues(789,kernel / workqueue.c:1204)

driver_init(793,drivers / base / init.c:20)

do_initcalls(796,init / main.c:769)/ *调用所有子系统的初始化函数* /

prepare_namespace(906,init / do_mounts.c:366)

initrd_load(399,init / do_mounts_initrd.c:107)

rd_load_image(117,init / do_mounts_rd.c:158)/ *如果指定了initrd * /

identity_ramdisk_image(179,init / do_mounts_rd.c:53)

handle_initrd(119,init / do_mounts_initrd.c:37)/ *如果rd_load_image成功* /

mount_block_root(45,init / do_mounts.c:233)

do_mount_root(247,初始/do_mounts.:218)

mount_root(417,init / do_mounts.c:334)/ *如果未指定initrd * /

mount_block_root(359,init / do_mounts.c:233)

do_mount_root(247,init / do_mounts.c:218)

init_post(915,init / main.c:816)

run_init_process(847,init / main.c:807)

kernel_execve(810,arch / arm / kernel / sys_arm.c:81)

用户空间

init()/ *用户空间/ sbin / init * /

引导程序:

引导加载程序是一个小程序,它将内核映像加载到RAM中并引导内核映像。这也称为引导程序,因为它通过加载操作系统来拉起(拉起)系统。 Bootloader在任何其他软件启动之前启动并初始化处理器,并使CPU准备执行诸如操作系统之类的程序。大多数处理器都有一个默认地址,在接通电源或复位板后,将从该地址获取代码的第一个字节。硬件设计人员使用此信息将引导程序代码存储在该地址的ROM或闪存中。由于它应该初始化cpu并应该运行位于特定于体系结构的程序,因此地址引导加载程序高度依赖于处理器,并且特定于电路板。每个嵌入式主板都带有一个引导程序,用于将内核映像或独立应用程序下载到板上并开始执行内核映像或应用程序。当处理器板上电时,将执行Bootloader。基本上,它将具有一些最小的功能来加载映像并启动它。

也可以使用硬件调试接口(例如JTAG)来控制系统。通过指示处理器核执行对非易失性存储器进行编程的必要动作,该接口可用于将引导加载程序写入可引导的非易失性存储器(例如闪存)。通常是第一次下载基本的引导程序并进行一些恢复过程。 JTAG是许多主板供应商提供的标准且流行的接口。一些微控制器提供了特殊的硬件接口,这些接口不能用于对系统进行任意控制或直接运行代码,而是通过简单的协议将引导代码插入可引导的非易失性存储器(例如闪存)中。然后在制造阶段,使用此类接口将启动代码(可能还有其他代码)注入非易失性存储器中。系统复位后,微控制器开始执行编程到其非易失性存储器中的代码,就像普通处理器使用ROM进行引导一样。在许多情况下,此类接口是通过硬连线逻辑实现的。在其他情况下,可以通过从GPIO引脚在集成片内启动ROM中运行的软件来创建此类接口。

还有其他一些第三方引导加载程序可用,它们提供了丰富的功能集和简单的用户界面。您可以将这些第三方自举程序下载到板上,并使它们成为您板的默认自举程序。通常,主板供应商提供的引导程序会被这些第三方引导程序替换。可用的第三方boolader数量很少,其中一些是开源的(或免费的引导程序),有些是商业的。其中一些是Das U-Boot,Red boot,GRUB(用于台式机),LILO,Loadlin,bootsect-loader,SYSLINUX,EtherBoot和ELILO。

我们将U-boot引导加载程序作为我们的引导加载程序。 U-boot是嵌入式系统中广泛使用的引导加载程序。我们将解释u-boot-2010.03源代码。您可以从以下站点下载U-boot。 http://www.denx.de/wiki/U-Boot

U-boot的构建方式:

根据U-boot的配置,使用针对特定体系结构构建的交叉编译器编译所有汇编文件(.S)和C文件(.c),并将生成目标文件(.o)。所有这些目标文件都由链接器链接,并且将创建一个可执行文件。目标文件或可执行文件是节如.text,.data,.bss等的集合。目标文件和可执行文件的文件格式如elf。目标文件的所有部分都将基于称为链接程序脚本的脚本排列在可执行文件中。该脚本告诉运行时所有节将在内存中加载的位置。了解此脚本对于了解引导加载程序和内核的组成方式以及如何将引导加载程序或内核的不同部分加载到内存中非常重要。

通常,运行(执行)程序时,加载程序将读取可执行文件,并将可执行文件的不同部分加载到指定的内存位置,然后开始执行链接描述文件中指定的启动函数(入口点)。但是,如果您要运行(加载)引导加载程序,则不会有任何加载程序(基本上是为了了解文件格式)将可执行文件的不同部分加载到内存中。然后,您需要使用一个名为objcopy的工具,它将从可执行文件中获取所有部分,并创建一个没有任何文件格式的二进制文件。该二进制文件可以加载到内存中并执行,或者可以在特定地址(特定于体系结构)写入ROM中,该特定地址在板上供电时将由cpu执行。

假设基于U-boot配置,将编译所有文件并创建目标文件。 U-boot生成文件使用以下链接程序脚本(特定于体系结构)来构建可执行文件。

档案:cpu / arm920t / u-boot.lds

32 OUTPUT_FORMAT(“ elf32-littlearm”,“ elf32-littlearm”,“ elf32-littlearm”)

33 OUTPUT_ARCH(手臂)

34 ENTRY(_start)

35节

36 {

37. = 0x00000000;

38

39. = ALIGN(4);

40.文字:

41 {

42 cpu / arm920t / start.o(.text)

43 *(。text)

44}

4546. = ALIGN(4);

47.rodata:{*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata *)))}

48

49. = ALIGN(4);

50.data:{*(。data)}

51

52. = ALIGN(4);

53.got:{*(。got)}

54

55. = .;

第56章(许我倾城)

57.u_boot_cmd:{*(。u_boot_cmd)}

第58章(许我倾城)

59

60. = ALIGN(4);

第61章

62.bss(NOLOAD):{*(。bss)。 = ALIGN(4); }

63 _end = .;

64}

第32行中的OUTPUT_FORMAT指定可执行文件的文件格式。这里的可执行文件格式为elf32,字节序为little endian。第33行的OUTPUT_ARCH指定运行此代码的体系结构。第34行中的ENTRY指定u-boot程序的启动功能(入口点)。此处的入口点是_start。

第35行中的SECTIONS定义了如何在可执行文件中映射不同的节。加载程序使用本节中指定的地址将程序的不同部分加载到内存中。

‘。’第37行中的起始地址指定了以下部分应加载的起始地址。在这种情况下,起始地址为0x00000000。在#39行之后,内存按4个字节对齐,.text节在#40行之后。

40.文字:

41 {

42 cpu / arm920t / start.o(.text)

43 *(。text)

44}

在“。”位置(0x00000000),将映射cpu / arm920t / start.o中的代码,并遵循所有其他目标(.o)文件的.text部分中的代码。 cpu / arm920t / start.o包含_start()函数(以汇编语言表示),该函数是该程序的入口点。

现在 ‘。’将为0x00000000 + sizeof(.text)。同样,内存按4个字节对齐,.rodata节位于第47行。

。 = ALIGN(4);

47.rodata:{*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata *)))}

所有目标文件中的.rodata节都映射到该地址。遵循.data和.git部分。

49. = ALIGN(4);

50.data:{*(。data)}

51

52. = ALIGN(4);

53.got:{*(。got)}

每个U-boot命令都是“ cmd_tbl_t”类型的对象,其中包含命令名称,帮助字符串和在运行此命令时要执行的功能指针。所有这些命令对象都顺序放置在内存中。每个命令对象都内置在目标文件中名为U.boot_cmd的U-boot定义的部分中。这些all.u_boot_cmd节放在上述节(.data和.git)之后。

。 = .;

第56章(许我倾城)

57.u_boot_cmd:{*(。u_boot_cmd)}

第58章(许我倾城)

__u_boot_cmd_start包含命令对象的开头,而__u_boot_cmd_end包含命令对象的结尾。

接下来是.bss(未初始化的全局变量)部分。

60. = ALIGN(4);

第61章

62.bss(NOLOAD):{*(。bss)。 = ALIGN(4); }

63 _end = .;

__bss_start指向.bss的起始地址,而_end包含所有节的结尾。

使用此链接器脚本链接器将生成一个名为u-boot的可执行文件。 Objcopy工具用于从u-boot可执行文件生成二进制文件。

u-boot.bin:u-boot

$(OBJCOPY)$ {OBJCFLAGS} -O二进制$