Arm linux 内核移植及系统初始化过程分析

来源:互联网 发布:淘一兔淘宝信誉查号 编辑:程序博客网 时间:2024/05/02 04:39
 

本文主要介绍内核移植过程中涉及文件的分布及其用途,以及简单介绍系统的初始化过程。整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第二阶段主要是对系统中的一些基础设施进行初始化;最后则是更高层次的初始化,如根设备和外部设备的初始化。了解系统的初始化过程,有益于更好地移植内核。

 

1.  内核移植涉及文件分布介绍

1.1.   内核移植涉及的头文件

/linux-2.6.18.8/include

[root@localhost include]# tree -L 1

.

|-- Kbuild

|-- acpi

|-- asm -> asm-arm

|-- asm-alpha

|-- asm-arm   ------------------------------->(1)

|-- asm-sparc

|-- asm-sparc64

|-- config

|-- keys

|-- linux          ------------------------------->(2)

|-- math-emu

|-- media

|-- mtd

|-- net

|-- pcmcia

|-- rdma

|-- rxrpc

|-- scsi

|-- sound

`-- video

 

内核移植过程中涉及到的头文件包括处理器相关的头文件(1)和处理器无关的头文件(2)。

 

1.2.   内核移植涉及的源文件

/linux-2.6.18.8/arch/arm

[root@localhost arm]# tree -L 1

.

|-- Kconfig

|-- Kconfig-nommu

|-- Kconfig.debug

|-- Makefile

|-- boot  ------------------------------->(2)

|-- common

|-- configs

|-- kernel  ------------------------------->(3)

|-- lib

|-- mach-at91rm9200

……

|-- mach-omap1

|-- mach-omap2

|-- mach-realview

|-- mach-rpc

|-- mach-s3c2410   ------------------------------->(4)

|-- mach-sa1100

|-- mach-versatile

|-- mm    ------------------------------->(5)

|-- nwfpe

|-- oprofile

|-- plat-omap

|-- tools    ------------------------------->(1)

`-- vfp

 

(1)

/linux-2.6.18.8/arch/arm/tools

[root@localhost tools]# tree -L 1

.

|-- Makefile

|-- gen-mach-types

`-- mach-types

 

Mach-types 文件定义了不同系统平台的系统平台号。移植linux内核到新的平台上需要对新的平台登记系统平台号。

 

Mach-types文件格式如下:

# machine_is_xxx   CONFIG_xxxx             MACH_TYPE_xxx        number

s3c2410             ARCH_S3C2410           S3C2410                        182

smdk2410          ARCH_SMDK2410 SMDK2410                   193

 

之所以需要这些信息,是因为脚本文件linux/arch/arm/tools/gen-mach-types需要linux/arch/tools/mach-types来产生linux/include/asm-arm/mach-types.h文件,该文件中设置了一些宏定义,需要这些宏定义来为目标系统选择合适的代码。

 

(2)

linux-2.6.18.8/arch/arm/boot/compressed

[root@localhost compressed]# tree -L 1

.

|-- Makefile

|-- Makefile.debug

|-- big-endian.S

|-- head-at91rm9200.S

|-- head.S

|-- ll_char_wr.S

|-- misc.c

|-- ofw-shark.c

|-- piggy.S

`-- vmlinux.lds.in

 

Head.s 是内核映像的入口代码,是自引导程序。自引导程序包含一些初始化程序,这些程序都是体系结构相关的。在对系统作完初始化设置工作后,调用misc.c文件中的decompress_kernel()函数解压缩内核映像到指定的位置,然后跳转到kernel的入口地址。

 

Vmlinux.lds.in用来生成内核映像的内存配置文件。

 

(3)

linux-2.6.18.8/arch/arm/kernel

[root@localhost kernel]# tree -L 1

.

|-- Makefile

|-- apm.c

|-- armksyms.c

|-- arthur.c

|-- asm-offsets.c

|-- bios32.c

|-- calls.S

|-- dma.c

|-- ecard.c

|-- entry-armv.S

|-- entry-common.S

|-- entry-header.S

|-- fiq.c

|-- head-common.S

|-- head-nommu.S

|-- head.S

|-- init_task.c

|-- io.c

|-- irq.c

|-- isa.c

|-- module.c

|-- process.c

|-- ptrace.c

|-- ptrace.h

|-- semaphore.c

|-- setup.c

|-- smp.c

|-- sys_arm.c

|-- time.c

|-- traps.c

`-- vmlinux.lds.S

 

内核入口处也是由一段汇编语言实现的,由head.s和head-common.s两个文件组成。

Head.s 是内核的入口文件, 在head.s的末尾处 #include "head-common.S"。 经过一系列的初始化后,跳转到linux-2.6.18.8/init/main.c中的start_kernel()函数中,开始内核的基本初始化过程。

 

 

/linux-2.6.18.8/init

[root@localhost init]# tree

.

|-- Kconfig

|-- Makefile

|-- calibrate.c

|-- do_mounts.c

|-- do_mounts.h

|-- do_mounts_initrd.c

|-- do_mounts_md.c

|-- do_mounts_rd.c

|-- initramfs.c

|-- main.c

`-- version.c

 

(4)

/linux-2.6.18.8/arch/arm/mach-s3c2410

[root@localhost mach-s3c2410]# tree -L 1

.

|-- Kconfig

|-- Makefile

|-- Makefile.boot

|-- bast-irq.c

|-- bast.h

|-- clock.c

|-- clock.h

|-- common-smdk.c

|-- common-smdk.h

|-- cpu.c

|-- cpu.h

|-- devs.c

|-- devs.h

|-- dma.c

|-- gpio.c

|-- irq.c

|-- irq.h

|-- mach-anubis.c

|-- mach-smdk2410.c

|-- pm-simtec.c

|-- pm.c

|-- pm.h

|-- s3c2400-gpio.c

|-- s3c2400.h

|-- s3c2410-clock.c

|-- s3c2410-gpio.c

|-- s3c2410.c

|-- s3c2410.h

|-- sleep.S

|-- time.c

|-- usb-simtec.c

`-- usb-simtec.h

 

这个目录中的文件都是板级相关的,其中比较重要是如下几个:

linux/arch/arm/mach-s3c2410/cpu.c

linux/arch/arm/mach-s3c2410/common-smdk.c

linux/arch/arm/mach-s3c2410/devs.c

linux/arch/arm/mach-s3c2410/mach-smdk2410.c

linux/arch/arm/mach-s3c2410/Makefile.boot

linux/arch/arm/mach-s3c2410/s3c2410.c

 

2.  处理器和设备

这里主要介绍处理器和设备的描述和操作过程。设备描述在linux/arch/arm/mach-s3c2410/devs.c和linux/arch/arm/mach-s3c2410/common-smdk.c中实现。最后以nand flash为例具体介绍。

2.1.   处理器、设备描述

设备描述主要两个结构体完成:struct resource和struct platform_device。

先来看看着两个结构体的定义:

struct resource {

       resource_size_t start;

       resource_size_t end;

       const char *name;

       unsigned long flags;

       struct resource *parent, *sibling, *child;

};

 

Resource结构体主要是描述了设备在系统中的起止地址、名称、标志以及为了链式描述方便指向本结构体类型的指针。Resource定义的实例将被添加到platform_device结构体对象中去。

 

struct platform_device {

       const char      * name;

       u32         id;

       struct device   dev;

       u32         num_resources;

       struct resource       * resource;

};

 

Platform_device结构体包括结构体的名称、ID号、平台相关的信息、设备的数目以及上面定义的resource信息。Platform_device结构对象将被直接通过设备操作函数注册导系统中去。具体注册和注销过程在下一节介绍。

 

2.2.   处理器、设备操作

(1) int platform_device_register(struct platform_device * pdev);    注册设备

(2) void platform_device_unregister(struct platform_device * pdev); 注销设备

(3) int platform_add_devices(struct platform_device **devs, int num);添加设备,通过调用上面两个函数实现。

2.3.   添加Nand flash设备

下面以nand flash 设备的描述为例,具体介绍下设备的描述和注册过程。

 

// resource结构体实例s3c_nand_resource 对nand flash 控制器描述,包括控制器的起止地址和标志。

static struct resource s3c_nand_resource[] = {

       [0] = {

              .start = S3C2410_PA_NAND,

              .end   = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,

              .flags = IORESOURCE_MEM,

       }

};

 

//platform_device结构体实例s3c_device_nand定义了设备的名称、ID号并把resource对象作为其成员之一。

struct platform_device s3c_device_nand = {

       .name               = "s3c2410-nand",

       .id             = -1,

       .num_resources       = ARRAY_SIZE(s3c_nand_resource),

       .resource   = s3c_nand_resource,

};

 

// nand flash 的分区情况,由mtd_partition结构体定义。

static struct mtd_partition smdk_default_nand_part[] = {

       [0] = {

              .name      = "Boot Agent",

              .size = SZ_16K,

              .offset     = 0,

       },

       [1] = {

              .name      = "S3C2410 flash partition 1",

              .offset = 0,

              .size = SZ_2M,

       },

       [2] = {

              .name      = "S3C2410 flash partition 2",

              .offset = SZ_4M,

              .size = SZ_4M,

       },

       [3] = {

              .name      = "S3C2410 flash partition 3",

              .offset     = SZ_8M,

              .size = SZ_2M,

       },

       [4] = {

              .name      = "S3C2410 flash partition 4",

              .offset = SZ_1M * 10,

              .size = SZ_4M,

       },

       [5] = {

              .name      = "S3C2410 flash partition 5",

              .offset     = SZ_1M * 14,

              .size = SZ_1M * 10,

       },

       [6] = {

              .name      = "S3C2410 flash partition 6",

              .offset     = SZ_1M * 24,

              .size = SZ_1M * 24,

       },

       [7] = {

              .name      = "S3C2410 flash partition 7",

              .offset = SZ_1M * 48,

              .size = SZ_16M,

       }

};

 

static struct s3c2410_nand_set smdk_nand_sets[] = {

       [0] = {

              .name             = "NAND",

              .nr_chips = 1,

              .nr_partitions  = ARRAY_SIZE(smdk_default_nand_part),

              .partitions       = smdk_default_nand_part,

       },

};

 

/* choose a set of timings which should suit most 512Mbit

 * chips and beyond.

*/

 

static struct s3c2410_platform_nand smdk_nand_info = {

       .tacls              = 20,

       .twrph0          = 60,

       .twrph1          = 20,

       .nr_sets   = ARRAY_SIZE(smdk_nand_sets),

       .sets        = smdk_nand_sets,

};

 

/* devices we initialise */

// 最后将nand flash 设备加入到系统即将注册的设备集合中。

static struct platform_device __initdata *smdk_devs[] = {

       &s3c_device_nand,

       &smdk_led4,

       &smdk_led5,

       &smdk_led6,

       &smdk_led7,

};

 

然后通过smdk_machine_init()函数,调用设备添加函数platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)) 完成设备的注册。具体过程参见系统初始化的相关部分。

3.  系统初始化

3.1.   系统初始化的主干线

Start_kernel() èsetup_arch() èreset_init() è kernel_thread(init …) è init() è do_basic_setup() èdriver_init() è do_initcall()

 

Start_kernel()函数负责初始化内核各个子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。Start_kernel()函数在init/main.c中实现。

 

asmlinkage void __init start_kernel(void)

{

       char * command_line;

       extern struct kernel_param __start___param[], __stop___param[];

 

       smp_setup_processor_id();

 

       /*

        * Need to run as early as possible, to initialize the

        * lockdep hash:

        */

       lockdep_init();

 

       local_irq_disable();

       early_boot_irqs_off();

       early_init_irq_lock_class();

 

/*

 * Interrupts are still disabled. Do necessary setups, then

 * enable them

 */

       lock_kernel();

       boot_cpu_init();

       page_address_init();

       printk(KERN_NOTICE);

       printk(linux_banner);

       setup_arch(&command_line);

 //setup processor and machine and destinate some pointers for do_initcalls() functions

// for example init_machine pointer is initialized with smdk_machine_init() function , and //init_machine() function is called by customize_machine(), and the function is processed by //arch_initcall(fn). Therefore  smdk_machine_init() is issured.    by edwin

       setup_per_cpu_areas();

       smp_prepare_boot_cpu();     /* arch-specific boot-cpu hooks */

 

       /*

        * Set up the scheduler prior starting any interrupts (such as the

        * timer interrupt). Full topology setup happens at smp_init()

        * time - but meanwhile we still have a functioning scheduler.

        */

       sched_init();

       /*

        * Disable preemption - early bootup scheduling is extremely

        * fragile until we cpu_idle() for the first time.

        */

       preempt_disable();

       build_all_zonelists();

       page_alloc_init();

       printk(KERN_NOTICE "Kernel command line: %s/n", saved_command_line);

       parse_early_param();

       parse_args("Booting kernel", command_line, __start___param,

                 __stop___param - __start___param,

                 &unknown_bootoption);

       sort_main_extable();

       unwind_init();

       trap_init();

       rcu_init();

       init_IRQ();

       pidhash_init();

       init_timers();

       hrtimers_init();

       softirq_init();

       timekeeping_init();

       time_init();

       profile_init();

       if (!irqs_disabled())

              printk("start_kernel(): bug: interrupts were enabled early/n");

       early_boot_irqs_on();

       local_irq_enable();

 

       /*

        * HACK ALERT! This is early. We're enabling the console before

        * we've done PCI setups etc, and console_init() must be aware of

        * this. But we do want output early, in case something goes wrong.

        */

       console_init();

       if (panic_later)

              panic(panic_later, panic_param);

 

       lockdep_info();

 

       /*

        * Need to run this when irqs are enabled, because it wants

        * to self-test [hard/soft]-irqs on/off lock inversion bugs

        * too:

        */

       locking_selftest();

 

#ifdef CONFIG_BLK_DEV_INITRD

       if (initrd_start && !initrd_below_start_ok &&

                     initrd_start < min_low_pfn << PAGE_SHIFT) {

              printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "

                  "disabling it./n",initrd_start,min_low_pfn << PAGE_SHIFT);

              initrd_start = 0;

       }

#endif

       vfs_caches_init_early();

       cpuset_init_early();

       mem_init();

       kmem_cache_init();

       setup_per_cpu_pageset();

       numa_policy_init();

       if (late_time_init)

              late_time_init();

       calibrate_delay();

       pidmap_init();

       pgtable_cache_init();

       prio_tree_init();

       anon_vma_init();

#ifdef CONFIG_X86

       if (efi_enabled)

              efi_enter_virtual_mode();

#endif

       fork_init(num_physpages);

       proc_caches_init();

       buffer_init();

       unnamed_dev_init();

       key_init();

       security_init();

       vfs_caches_init(num_physpages);

       radix_tree_init();

       signals_init();

       /* rootfs populating might need page-writeback */

       page_writeback_init();

#ifdef CONFIG_PROC_FS

       proc_root_init();

#endif

       cpuset_init();

       taskstats_init_early();

       delayacct_init();

 

       check_bugs();

 

       acpi_early_init(); /* before LAPIC and SMP init */

 

       /* Do the rest non-__init'ed, we're now alive */

       rest_init();

}

 

分析start_kernel()源码, 其中setup_arch() 和 reset_init()是两个比较关键的函数。下面将具体分析这两个函数。

3.2.   setup_arch()函数分析

首先我们来分析下setup_arch()函数。

Setup_arch()函数主要工作是安装cpu和machine,并为start_kernel()后面的初始化函数指针指定值。

其中setup_processor()函数调用linux/arch/arm/kernel/head_common.S 中的lookup_processor_type函数查询处理器的型号并安装。

 

Setup_machine()函数调用inux/arch/arm/kernel/head_common.S 中的lookup_machine_type(__machine_arch_type)函数根据体系结构号__machine_arch_type,在__arch_info_begin和__arch_info_end段空间查询体系结构。问题是__machine_arch_type是在什么时候赋的初值?__arch_info_begin和__arch_info_end段空间到底放的是什么内容?

__machine_arch_type是一个全局变量,在linux/boot/decompress/misc.c的解压缩函数中得以赋值。

decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p, int arch_id)

{

       __machine_arch_type    = arch_id;

}

 

__arch_info_begin和__arch_info_end段空间到底放的内容由链接器决定,存放是.arch.info.init段的内容。这个段是通过段属性__attribute__指定的。Grep一下.arch.info.init 得到./include/asm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = {       / 在linux/include/asm-arm/mach/arch.h 中发现MACHINE_START宏定义。

 

#define MACHINE_START(_type,_name)                  /

static const struct machine_desc __mach_desc_##_type    /

 __attribute_used__                                   /

 __attribute__((__section__(".arch.info.init"))) = {     /

       .nr          = MACH_TYPE_##_type,            /

       .name             = _name,

 

#define MACHINE_END                            /

};

 

inux/arch/arm/mach-s3c2410/mach-smdk2410.c中对.arch.info.init段的初始化如下。

MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch

                                * to SMDK2410 */

       /* Maintainer: Jonas Dietsche */

       .phys_io  = S3C2410_PA_UART,

       .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

       .boot_params  = S3C2410_SDRAM_PA + 0x100,

       .map_io          = smdk2410_map_io,

       .init_irq   = s3c24xx_init_irq,

       .init_machine  = smdk_machine_init,

       .timer             = &s3c24xx_timer,

MACHINE_END

 

由此可见在.arch.info.init段内存放了__desc_mach_desc_SMDK2410结构体。初始化了相应的初始化函数指针。问题又来了, 这些初始化指针函数是什么时候被调用的呢?

分析发现,不一而同。

如s3c24xx_init_irq()函数是通过start_kernel()里的init_IRQ()函数调用init_arch_irq()实现的。因为在MACHINE_START结构体中  .init_irq       = s3c24xx_init_irq,而在setup_arch()函数中init_arch_irq = mdesc->init_irq, 所以调用init_arch_irq()就相当于调用了s3c24xx_init_irq()。

又如smdk_machine_init()函数的初始化。在MACHINE_START结构体中,函数指针赋值,.init_machine      = smdk_machine_init。而init_machine()函数被linux/arch/arm/kernel/setup.c文件中的customize_machine()函数调用并被arch_initcall(Fn)宏处理,arch_initcall(customize_machine)。 被arch_initcall(Fn)宏处理过函数将linux/init/main.c

do_initcalls()函数调用。 具体参看下边的部分。

 

void __init setup_arch(char **cmdline_p)

{

       struct tag *tags = (struct tag *)&init_tags;

       struct machine_desc *mdesc;

       char *from = default_command_line;

 

       setup_processor();

       mdesc = setup_machine(machine_arch_type);//machine_arch_type =SMDK2410  by edwin

       machine_name = mdesc->name;

 

       if (mdesc->soft_reboot)

              reboot_setup("s");

 

       if (mdesc->boot_params)

              tags = phys_to_virt(mdesc->boot_params);

 

       /*

        * If we have the old style parameters, convert them to

        * a tag list.

        */

       if (tags->hdr.tag != ATAG_CORE)

              convert_to_tag_list(tags);

       if (tags->hdr.tag != ATAG_CORE)

              tags = (struct tag *)&init_tags;

 

       if (mdesc->fixup)

              mdesc->fixup(mdesc, tags, &from, &meminfo);

 

       if (tags->hdr.tag == ATAG_CORE) {

              if (meminfo.nr_banks != 0)

                     squash_mem_tags(tags);

              parse_tags(tags);

       }

 

       init_mm.start_code = (unsigned long) &_text;

       init_mm.end_code   = (unsigned long) &_etext;

       init_mm.end_data   = (unsigned long) &_edata;

       init_mm.brk       = (unsigned long) &_end;

 

       memcpy(saved_command_line, from, COMMAND_LINE_SIZE);

       saved_command_line[COMMAND_LINE_SIZE-1] = '/0';

       parse_cmdline(cmdline_p, from);

       paging_init(&meminfo, mdesc);

       request_standard_resources(&meminfo, mdesc);

 

#ifdef CONFIG_SMP

       smp_init_cpus();

#endif

 

       cpu_init();

 

       /*

        * Set up various architecture-specific pointers

        */

       init_arch_irq = mdesc->init_irq;

       system_timer = mdesc->timer;

       init_machine = mdesc->init_machine;

 

#ifdef CONFIG_VT

#if defined(CONFIG_VGA_CONSOLE)

       conswitchp = &vga_con;

#elif defined(CONFIG_DUMMY_CONSOLE)

       conswitchp = &dummy_con;

#endif

#endif

}

3.3.   rest_init()函数分析

下面我们来分析下rest_init()函数。

Start_kernel()函数负责初始化内核各子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。在init内核线程中,将执行下列init()函数的程序。Init()函数负责完成根文件系统的挂接、初始化设备驱动程序和启动用户空间的init进程等重要工作。

 

static void noinline rest_init(void)

       __releases(kernel_lock)

{

       kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);

       numa_default_policy();

       unlock_kernel();

 

       /*

        * The boot idle thread must execute schedule()

        * at least one to get things moving:

        */

       preempt_enable_no_resched();

       schedule();

       preempt_disable();

 

       /* Call into cpu_idle with preempt disabled */

       cpu_idle();

}

 

 

static int init(void * unused)

{

       lock_kernel();

       /*

        * init can run on any cpu.

        */

       set_cpus_allowed(current, CPU_MASK_ALL);

       /*

        * Tell the world that we're going to be the grim

        * reaper of innocent orphaned children.

        *

        * We don't want people to have to make incorrect

        * assumptions about where in the task array this

        * can be found.

        */

       child_reaper = current;

 

       smp_prepare_cpus(max_cpus);

 

       do_pre_smp_initcalls();

 

       smp_init();

       sched_init_smp();

 

       cpuset_init_smp();

 

       /*

        * Do this before initcalls, because some drivers want to access

        * firmware files.

        */

       populate_rootfs();   //挂接根文件系统

 

       do_basic_setup();   //初始化设备驱动程序

 

       /*

        * check if there is an early userspace init.  If yes, let it do all

        * the work        //启动用户空间的init进程

        */

 

       if (!ramdisk_execute_command)

              ramdisk_execute_command = "/init";

 

       if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

              ramdisk_execute_command = NULL;

              prepare_namespace();

       }

 

       /*

        * Ok, we have completed the initial bootup, and

        * we're essentially up and running. Get rid of the

        * initmem segments and start the user-mode stuff..

        */

       free_initmem();

       unlock_kernel();

       mark_rodata_ro();

       system_state = SYSTEM_RUNNING;

       numa_default_policy();

 

       if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

              printk(KERN_WARNING "Warning: unable to open an initial console./n");

 

       (void) sys_dup(0);

       (void) sys_dup(0);

 

       if (ramdisk_execute_command) {

              run_init_process(ramdisk_execute_command);

              printk(KERN_WARNING "Failed to execute %s/n",

                            ramdisk_execute_command);

       }

 

       /*

        * We try each of these until one succeeds.

        *

        * The Bourne shell can be used instead of init if we are

        * trying to recover a really broken machine.

        */

       if (execute_command) {

              run_init_process(execute_command);

              printk(KERN_WARNING "Failed to execute %s.  Attempting "

                                   "defaults.../n", execute_command);

       }

       run_init_process("/sbin/init");

       run_init_process("/etc/init");

       run_init_process("/bin/init");

       run_init_process("/bin/sh");

 

       panic("No init found.  Try passing init= option to kernel.");

}

 

3.3.1. 挂接根文件系统

Linux/init/ramfs.c

void __init populate_rootfs(void)

{

       char *err = unpack_to_rootfs(__initramfs_start,

                      __initramfs_end - __initramfs_start, 0);

       if (err)

              panic(err);

#ifdef CONFIG_BLK_DEV_INITRD

       if (initrd_start) {

#ifdef CONFIG_BLK_DEV_RAM

              int fd;

              printk(KERN_INFO "checking if image is initramfs...");

              err = unpack_to_rootfs((char *)initrd_start,

                     initrd_end - initrd_start, 1);

              if (!err) {

                     printk(" it is/n");

                     unpack_to_rootfs((char *)initrd_start,

                            initrd_end - initrd_start, 0);

                     free_initrd();

                     return;

              }

              printk("it isn't (%s); looks like an initrd/n", err);

              fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);

              if (fd >= 0) {

                     sys_write(fd, (char *)initrd_start,

                                   initrd_end - initrd_start);

                     sys_close(fd);

                     free_initrd();

              }

#else

              printk(KERN_INFO "Unpacking initramfs...");

              err = unpack_to_rootfs((char *)initrd_start,

                     initrd_end - initrd_start, 0);

              if (err)

                     panic(err);

              printk(" done/n");

              free_initrd();

#endif

       }

#endif

}

 

3.3.2. 初始化设备驱动程序

linux/init/main.c

static void __init do_basic_setup(void)

{

       /* drivers will send hotplug events */

       init_workqueues();

       usermodehelper_init();

       driver_init();   /* 初始化驱动程序模型。调用驱动初始化函数初始化子系统。 */

 

#ifdef CONFIG_SYSCTL

       sysctl_init();

#endif

 

       do_initcalls();

}

 

 

linux/init/main.c

extern initcall_t __initcall_start[], __initcall_end[];

 

static void __init do_initcalls(void)

{

       initcall_t *call;

       int count = preempt_count();

 

       for (call = __initcall_start; call < __initcall_end; call++) {

              char *msg = NULL;

              char msgbuf[40];

              int result;

 

              if (initcall_debug) {

                     printk("Calling initcall 0x%p", *call);

                     print_fn_descriptor_symbol(": %s()",

                                   (unsigned long) *call);

                     printk("/n");

              }

 

              result = (*call)();

 

              ……

……

……

       }

 

       /* Make sure there is no pending stuff from the initcall sequence */

       flush_scheduled_work();

}

分析上面一段代码可以看出,设备的初始化是通过do_basic_setup()函数调用do_initcalls()函数,实现__initcall_start, __initcall_end段之间的指针函数执行的。而到底是那些驱动函数怎么会被集中到这个段内的呢?我们知道系统内存空间的分配是由链接器ld读取链接脚本文件决定。链接器将同样属性的文件组织到相同的段里面去,如所有的.text段都被放在一起。在链接脚本里面可以获得某块内存空间的具体地址。我们来看下linux-2.6.18.8/arch/arm/kernel/vmlinux.lds.S文件。由于文件过长,只贴出和__initcall_start, __initcall_end相关的部分。

__initcall_start = .;

                     *(.initcall1.init)

                     *(.initcall2.init)

                     *(.initcall3.init)

                     *(.initcall4.init)

                     *(.initcall5.init)

                     *(.initcall6.init)

                     *(.initcall7.init)

              __initcall_end = .;

从脚本文件中我们可以看出, 在__initcall_start, __initcall_end之间放置的是属行为(.initcall*.init)的函数数据 。在linux/include/linux/init.h文件中可以知道,(.initcall*.init)属性是由__define_initcall(level, fn)宏设定的。

 

#define __define_initcall(level,fn) /

       static initcall_t __initcall_##fn __attribute_used__ /

       __attribute__((__section__(".initcall" level ".init"))) = fn

 

#define core_initcall(fn)        __define_initcall("1",fn)

#define postcore_initcall(fn)         __define_initcall("2",fn)

#define arch_initcall(fn)        __define_initcall("3",fn)

#define subsys_initcall(fn)            __define_initcall("4",fn)

#define fs_initcall(fn)                   __define_initcall("5",fn)

#define device_initcall(fn)             __define_initcall("6",fn)

#define late_initcall(fn)          __define_initcall("7",fn)

#define __initcall(fn)      device_initcall(fn)

 

由此可以判断,所有的设备驱动函数都必然通过*_initcall(fn)宏的处理。以此为入口,可以查询所有的设备驱动。

core_initcall(fn)

static int __init consistent_init(void)        linux/arch/arm/mm/consistent.c

static int __init v6_userpage_init(void)      linux/arch/arm/mm/copypage-v6.c

static int __init init_dma(void)             linux/arch/arm/kernel/dma.c

static int __init s3c2410_core_init(void)     linux/arch/arm/mach-s3c2410/s3c2410.c

 

postcore_initcall(fn)

static int ecard_bus_init(void)                            linux/arch/arm/kernel/ecard.c

 

arch_initcall(fn)

static __init int bast_irq_init(void)                linux/arch/arm/mach-s3c2410/bast-irq.c

static int __init s3c_arch_init(void)              linux/arch/arm/mach-s3c2410/cpu.c

static __init int pm_simtec_init(void)         linux/arch/arm/mach-s3c2410/pm-simtec.c

static int __init customize_machine(void)     linux/arch/arm/kernel/setup.c

 

subsys_initcall(fn)

static int __init ecard_init(void)                   linux/arch/arm/kernel/ecard.c

int __init scoop_init(void)                           linux/arch/arm/common/scoop.c

static int __init topology_init(void)        linux/arch/arm/kernel/setup.c

 

fs_initcall(fn)

static int __init alignment_init(void)             linux/arch/arm/mm/alignment.c

 

device_initcall(fn)

static int __init leds_init(void)                     linux/arch/arm/kernel/time.c

static int __init timer_init_sysfs(void)          linux/arch/arm/kernel/time.c

 

late_initcall(fn)

static int __init crunch_init(void)                 arch/arm/kernel/crunch.c

static int __init arm_mrc_hook_init(void)     linux/arch/arm/kernel/traps.c

 

3.3.3. 启动用户空间的程序

原创粉丝点击