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)) 完成设备的注册。具体过程参见系统初始化的相关部分。 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() s
5、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838 //
for example init_machine pointer is initialized with
smdk_machine_init() , and //init_machine() is called by
customize_machine(), and the 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 ing 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) - " 6、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838 "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) >> 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); 8、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838 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进程 9、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838 */
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_deor_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__ \ 11、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838 __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
|