由驱动板级初始化发生的联想:内核解压,机器码匹配,uboot之bootm解析

#ifndef _LINUX_INIT_H
#define _LINUX_INIT_H

#include <linux/compiler.h>
。。。。。

/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init        __section(.init.text) __cold notrace
#define __initdata    __section(.init.data)
#define __initconst    __section(.init.rodata)
#define __exitdata    __section(.exit.data)
#define __exit_call    __used __section(.exitcall.exit)

/* modpost check for section mismatches during the kernel build.
 * A section mismatch happens when there are references from a
 * code or data section to an init section (both code or data).
 * The init sections are (for most archs) discarded by the kernel
 * when early init has completed so all such references are potential bugs.
 * For exit sections the same issue exists.
 * The following markers are used for the cases where the reference to
 * the *init / *exit section (code or data) is valid and will teach
 * modpost not to issue a warning.
 * The markers follow same syntax rules as __init / __initdata. */
#define __ref            __section(.ref.text) noinline
#define __refdata        __section(.ref.data)
#define __refconst       __section(.ref.rodata)

/* compatibility defines */
#define __init_refok     __ref
#define __initdata_refok __refdata
#define __exit_refok     __ref


#ifdef MODULE
#define __exitused
#else
#define __exitused  __used
#endif
。。。。。。

#ifndef __ASSEMBLY__
/*
 * Used for initialization calls..
 */
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);

extern initcall_t __con_initcall_start[], __con_initcall_end[];
extern initcall_t __security_initcall_start[], __security_initcall_end[];

/* Used for contructor calls. */
typedef void (*ctor_fn_t)(void);

/* Defined in init/main.c */
extern int do_one_initcall(initcall_t fn);
extern char __initdata boot_command_line[];
extern char *saved_command_line;
extern unsigned int reset_devices;

。。。。。。

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)        __define_initcall("early",fn,early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn)        __define_initcall("0",fn,0)

#define core_initcall(fn)        __define_initcall("1",fn,1)
#define core_initcall_sync(fn)        __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)        __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)    __define_initcall("2s",fn,2s)
#define arch_initcall(fn)        __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)        __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)        __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)
#define fs_initcall(fn)            __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)        __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)        __define_initcall("6",fn,6)
#define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)
#define late_initcall(fn)        __define_initcall("7",fn,7)
#define late_initcall_sync(fn)        __define_initcall("7s",fn,7s)

#define __initcall(fn) device_initcall(fn)
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
#define __exitcall(fn) \
    static exitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn) \
    static initcall_t __initcall_##fn \
    __used __section(.con_initcall.init) = fn

#define security_initcall(fn) \
    static initcall_t __initcall_##fn \
    __used __section(.security_initcall.init) = fn

struct obs_kernel_param {
    const char *str;
    int (*setup_func)(char *);
    int early;
};

/*
 * Only for really core code.  See moduleparam.h for the normal way.
 *
 * Force the alignment so the compiler doesn't space elements of the
 * obs_kernel_param "array" too far apart in .init.setup.
 */
#define __setup_param(str, unique_id, fn, early)            \
    static const char __setup_str_##unique_id[] __initconst    \
        __aligned(1) = str; \
    static struct obs_kernel_param __setup_##unique_id    \
        __used __section(.init.setup)            \
        __attribute__((aligned((sizeof(long)))))    \
        = { __setup_str_##unique_id, fn, early }

#define __setup(str, fn)                    \
    __setup_param(str, fn, fn, 0)

/* NOTE: fn is as per module_param, not __setup!  Emits warning if fn
 * returns non-zero. */
#define early_param(str, fn)                    \
    __setup_param(str, fn, fn, 1)

/* Relies on boot_command_line being set */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);
#endif /* __ASSEMBLY__ */

/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 * 
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)    __initcall(x);

/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 * 
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x)    __exitcall(x);

#else /* MODULE */

/* Don't use these in modules, but some people do... */
#define early_initcall(fn)        module_init(fn)
#define core_initcall(fn)        module_init(fn)
#define postcore_initcall(fn)        module_init(fn)
#define arch_initcall(fn)        module_init(fn)
#define subsys_initcall(fn)        module_init(fn)
#define fs_initcall(fn)            module_init(fn)
#define device_initcall(fn)        module_init(fn)
#define late_initcall(fn)        module_init(fn)

#define security_initcall(fn)        module_init(fn)

/* Each module must use one module_init(). */
#define module_init(initfn)                    \
    static inline initcall_t __inittest(void)        \
    { return initfn; }                    \
    int init_module(void) __attribute__((alias(#initfn)));

/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)                    \
    static inline exitcall_t __exittest(void)        \
    { return exitfn; }                    \
    void cleanup_module(void) __attribute__((alias(#exitfn)));

#define __setup_param(str, unique_id, fn)    /* nothing */
#define __setup(str, func)             /* nothing */
#endif
。。。。。。。
#if defined(MODULE) || defined(CONFIG_HOTPLUG)
#define __devexit_p(x) x
#else
#define __devexit_p(x) NULL
#endif

#ifdef MODULE
#define __exit_p(x) x
#else
#define __exit_p(x) NULL
#endif

#endif /* _LINUX_INIT_H */



这部分代码来自linux内核2.6.32的init.h文件;

在这里可以看到两个最常见的初始化函数:#define module_init(x)    __initcall(x);和#define module_exit(x)    __exitcall(x);

而继续看下去:#define __initcall(fn) device_initcall(fn)这个回归函数是设备模块的初始化入口所在。

继续:#define device_initcall(fn)        __define_initcall("6",fn,6),这又说明了什么呢?

其实在Linux内核中驱动以及一些模块,往往具有依赖性,比如某个模块需要另一个模块存在才能完成注册,比如I2C的适配器往往需要使用subsys来完成先注册,这样后续I2C设备的注册初始化。

这里Init.h说明了这些函数的先后,而之所以会先后关系,在于编译器的编译时候生成的section表,如下的Macro定义

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn
可以看到这个进一步变成了该宏定义_initcall_6_6_used 并且说明了_section要位于_initcall6.init段中,即fn(初始化模块的init函数名,函数的入口地址)放入到该section中去。这些都是编译器来完成的。

至于如何运行上述section中的函数指针的话,主要由init.c中的start_kernel中的子进程中的kernel_init来完成,内部会调用do_basic_setup-》do_initcall来完成内核维护的上述含有各等级函数指针,安initcall.1.init,initcall.2.init...................到7的过程。

这样整个内核在进入用户态时就完成好了设备的相关模块和驱动的初始化,另外exit的的实现也类似




2.至于板级的Init过程,这里主要分析的是arch/arm/mach_omap2.c函数。在这个函数内部有如下代码:

MACHINE_START(OMAP3_BEAGLE, "OMAP3 Beagle Board")
    /* Maintainer: Syed Mohammed Khasim - http://beagleboard.org */
    .phys_io    = 0x48000000,
    .io_pg_offst    = ((0xfa000000) >> 18) & 0xfffc,
    .boot_params    = 0x80000100,
    .map_io        = omap3_beagle_map_io,
    .init_irq    = omap3_beagle_init_irq,
    .init_machine    = omap3_beagle_init,
    .timer        = &omap_timer,
MACHINE_END

函数的宏定义如下:
/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};
该宏是定义了一个结构体描述板子的信息。一般的宏定义在未换行前都是展开后

static const struct machine_desc __mach_desc_OMAP_BEAGLE

 __used  __attribute__((__section__(".arch.info.init"))) = {   
    .nr        = MACH_TYPE_##_type,        //  #define MACH_TYPE_OMAP3_BEAGLE  1546 定义在include/asm-arm/mach-types.h这个也称之为机器码和uboo(uboot对应的机器码存在arch/arm/include/asm/mach-types.h中)t里面的一定要匹配相同的才行。
    .name        =OMAP3 Beagle Board, 

  .phys_io    = 0x48000000,
    .io_pg_offst    = ((0xfa000000) >> 18) & 0xfffc,
    .boot_params    = 0x80000100,
    .map_io        = omap3_beagle_map_io,
    .init_irq    = omap3_beagle_init_irq,
    .init_machine    = omap3_beagle_init,
    .timer        = &omap_timer,

};

简化后就是定义了一个如上的结构体对象,切放在section:arch.info.init段处。


下面回答init.c的内核启动代码出有函数setup_arch();调用    mdesc = setup_machine(machine_arch_type);就是返回一个machine_desc的结构体,内核按照上面的机器码type找到该结构体,在该函数中依次通过setup_machine->到    list = lookup_machine_type(nr)(该函数的代码位于arch/arm/kernel的head.s的汇编代码),最终找到对应的编译在上述section端(section:arch.info.init)的地方进行查找,涉及到相关查找的机制这里也不懂。

void __init setup_arch(char **cmdline_p)
{
    struct tag *tags = (struct tag *)&init_tags;
    struct machine_desc *mdesc;
    char *from = default_command_line;

    unwind_init();

    setup_processor();
    mdesc = setup_machine(machine_arch_type);
    machine_name = mdesc->name;//name = MACH_TYPE_OMAP3_BEAGLE 
......
}
static struct machine_desc * __init setup_machine(unsigned int nr)
{
	struct machine_desc *list;

	/*
	 * locate machine in the list of supported machines.
	 */
	list = lookup_machine_type(nr);
	if (!list) {
		printk("Machine configuration botched (nr %d), unable "
		       "to continue.\n", nr);
		while (1);
	}

	printk("Machine: %s\n", list->name);

	return list;
}
这个的#define machine_arch_type __machine_arch_type这个宏定义需要 在arch/arm/boot/misc.c里面才可以实现获取,

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

{
    output_data        = (uch *)output_start;    /* Points to kernel start */
    free_mem_ptr        = free_mem_ptr_p;
    free_mem_end_ptr    = free_mem_ptr_end_p;
    __machine_arch_type    = arch_id;

    arch_decomp_setup();

    makecrc();
    putstr("Uncompressing Linux...");
    gunzip();//完成内核代码的加压过程
    putstr(" done, booting the kernel.\n");
    return output_ptr;
}

接压缩内核部分代码时,传入的系统变量,而这个变量也就是从uboot那边传入的。这里还可以看到内核启动时的最基本解压过程,这个过程比start_kernel还早,因为只有将内核完全解压后才可以运行start_kernel.

内核启动时的打印信息如下:

Starting kernel ...

Uncompressing Linux.....................................................................................................................................................
[    0.000000] Linux version 2.6.32 (root@linux) (gcc version 4.3.3 (Sourcery G++ Lite 2009q1-203) ) #73 PREEMPT Sat Mar 2 17:27:37 CST 2013
[    0.000000] CPU: ARMv7 Processor [413fc082] revision 2 (ARMv7), cr=10c53c7f
[    0.000000] CPU: VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
[    0.000000] Machine: OMAP3 Beagle Board

uboot联想到的相关部分

其中Start Kernel .... 是在uboot 中显示出来的在do_bootm_linux中出现,这个函数也是完成内核在内存中的启动,使用bootm addr来完成内核启动,前提是内核已经存在于该地址上,也可以用于跑其他程序。只需把app下载带对应内存地址即可。

bootm这个uboot的命令要依次经过do_bootm->bootm_start->do_bootm_linux(该函数和加载的内核操作系统类型需要一样,如linux,vxworks,QNX等)这么个过程。

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	ulong		iflag;
	ulong		load_end = 0;
	int		ret;
	boot_os_fn	*boot_fn;
#ifndef CONFIG_RELOC_FIXUP_WORKS
	static int relocated = 0;
.....
	if (bootm_start(cmdtp, flag, argc, argv))
		return 1;

	/*
	 * We have reached the point of no return: we are going to
	 * overwrite all exception vector code, so we cannot easily
	 * recover from any failures any more...
	 */
	iflag = disable_interrupts();
.......

	ret = bootm_load_os(images.os, &load_end, 1);

	...

	boot_fn = boot_os[images.os.os];//do_bootm_linux

	if (boot_fn == NULL) {
		if (iflag)
			enable_interrupts();
		printf ("ERROR: booting os '%s' (%d) is not supported\n",
			genimg_get_os_name(images.os.os), images.os.os);
		show_boot_progress (-8);
		return 1;
	}

	arch_preboot_os();

	boot_fn(0, argc, argv, &images);//do_bootm_linux:prepare to start kernel (Starting kenel ...)

	.....
}

do_bootm_linux(){
....
#endif
	setup_end_tag (bd);
#endif

	/* we assume that the kernel is in place */
	printf ("\nStarting kernel ...\n\n");
}
从do_bootm_linux就可以进入内核的解压状态了,和uboot脱离了关系。但是这里可以知道uboot其实是将自己的机器码送入到了r1寄存器中,回到内核解压部分 arch/arm/boot/head.s中,在这里跳入解压过程。

在内核真正解压完成以后,就跳入start_kernel(arch/arm/kernel/common_head.S中调用该函数)中,进入C 语言的世界完成后续的操作回到setup_arch函数中,这样在start_kernel前已经完成了__machine_arch_type的相关初始化(在解压函数decompress_kernel中),当然这个机器码是来自uboot传入的,后续会以这个机器码为核心,获取结构体machine_desc,这个就是最初由编译指定放在section(arch.info.init)处的板级信息,由汇编级代码lookup_machine_type来完成遍历该端,获取该结构体对象,期间完成相关机器码的匹配。

以后内核就可以使用这个machine_desc这个对象了。

接下去分析的内容就是machine_desc的init_machine在哪里被调用,这个函数必须被最先初始化调用,因为有很多板级的设备需要被先注册信息?

代码位于:arch\arm\kernel\setup.c中

在函数setup_arch中:

void __init setup_arch(char **cmdline_p)
{
	struct tag *tags = (struct tag *)&init_tags;
	struct machine_desc *mdesc;
	char *from = default_command_line;

	unwind_init();

	setup_processor();
	mdesc = setup_machine(machine_arch_type);
	。。。。
	request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
	smp_init_cpus();
#endif

	cpu_init();
	tcm_init();

	/*
	 * Set up various architecture-specific pointers
	 */
	init_arch_irq = mdesc->init_irq;
	system_timer = mdesc->timer;
	init_machine = mdesc->init_machine;

。。。。。。
}
在上述函数中会将这个函数指针进行初始化赋值,在如下函数中会调用init_machine:

static int __init customize_machine(void)
{
	/* customizes platform devices, or adds new ones */
	if (init_machine)
		init_machine();
	return 0;
}
arch_initcall(customize_machine);

可以看到这部分的代码在这里执行,而且属于arch_initcall(3)调用,比其他的设备类型等级要高,先初始化,但是也需要在setup_arch中获取init_machine的函数指针。

总结出来的板级初始化调用过程如下:

start_kernel()--->setup_arch()(架构上面的初始化,获取板级信息,)--->到rest_init函数。

在rest_init函数中fork一个子线程带调用kernel_init在该函数中调用流程:do_basic_setup--->do_initcalls()--->customize_machine()--->xxx_machine_init(),

完成一次板级信息的初始化。其他的模块init也在do_initcalls()这里安不同级别完成注册和执行。

最后在Kernel_init函数中执行init_post,exec("/sbin/init");表明从内核空间正式进入用户空间,开始后续的各种etc下配置脚本执行和用户空间的相关初始化。




本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。

欢迎和大家交流。qq:1037701636 email: gzzaigcn2009@163.comgzzaigcn2012@gmail.com

 


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值