免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2573 | 回复: 3
打印 上一主题 下一主题

Arm linux 内核移植及系统初始化过程分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-01-06 00:25 |只看该作者 |倒序浏览
Arm linux 内核移植及系统初始化过程分析
Edwin. Rong   edwinrong@mxic.com.cn


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

1.        内核移植2.        涉及文件分布介绍
2.1.        内核移植2.2.        涉及的头文件
/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)。

2.3.        内核移植2.4.        涉及的源文件
/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

3.        处理器和设备4.       
这里主要介绍处理器和设备的描述和操作过程。设备描述在linux/arch/arm/mach-s3c2410/devs.c和linux/arch/arm/mach-s3c2410/common-smdk.c中实现。最后以nand flash为例具体介绍。
4.1.        处理器、设备4.2.        描述
设备描述主要两个结构体完成: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结构对象将被直接通过设备操作函数注册导系统中去。具体注册和注销过程在下一节介绍。

4.3.        处理器、设备4.4.        操作
(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);添加设备,通过调用上面两个函数实现。
4.5.        添加Nand flash设备4.6.       
下面以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)) 完成设备的注册。具体过程参见系统初始化的相关部分。

论坛徽章:
0
2 [报告]
发表于 2009-01-06 00:26 |只看该作者
5.        系统初始化
5.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()是两个比较关键的函数。下面将具体分析这两个函数。
5.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) >> 1 & 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
}
5.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.");
}

5.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
}

5.3.2.        初始化设备5.3.3.        驱动程序
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

5.3.4.        启动用户空间的程序

论坛徽章:
0
3 [报告]
发表于 2009-01-06 02:42 |只看该作者
沙发,好文。

论坛徽章:
0
4 [报告]
发表于 2009-01-06 08:54 |只看该作者

回复 #1 whereshall 的帖子

LZ学习的很有收获
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP