本文主要是介绍ARM linxu启动过程分析(四),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1. Linux内核启动第三阶段start_kernel:
内核从现在开始就进入C语言部分,内核启动第三阶段从init/main.c文件中的start_kernel()函数开始,到该函数结束。这一阶段对整个系统内存、cache、信号、设备等进行初始化,最后生成init进程后,调用cpu_idle()完成内核启动的第三阶段。Start_kernel()中调用了一系列的初始化函数,以完成kernel本身的设置。这些动作有些是公共的,有的则是需要配置才会执行的。
asmlinkage void __initstart_kernel(void)
{
char * command_line;
extern struct kernel_param__start___param[], __stop___param[];
/*中断是禁止的,作必要的设置之后,使能中断*/
lock_kernel();
/*如果内核配置成支持抢占,那么在这里禁止抢占,将0号进程的init_thread_info.preempt_count加1,如果配置成不支持抢占,那么内核全局自旋锁kernel_flag上锁*/
page_address_init(); /*ARM9不支持高端内存(>896MB),一般嵌入式产品也不会用高端内存,所以这里是空函数*/
printk(KERN_NOTICE);
/*将linux_banner的内容打印到log_buf缓冲区中去,等到串口或者其它终端初始化之后,在一次性打印到终端上去*/
printk(linux_banner);
setup_arch(&command_line);
/* setup_arch()原型在arch/arm/kernel/setup.c中,根据处理器硬件平台设置系统;
解析linux命令行参数,设置0号进程的内存描述结构init_mm,系统内存管理初始化,
统计并注册系统各种资源,其它项目的初始化 */
setup_per_cpu_areas();//为系统中的每个cpu的per_cpu变量申请空间
/* Mark the boot cpu "online"so that it can call console drivers in
*printk() and can access its per-cpu storage. */
smp_prepare_boot_cpu();
/*开启任何中断之前,初始化调度器.Full topology setup happens at smp_init()
* time- but meanwhile we still have a functioning scheduler.
初识化每个处理器的可运行进程队列,设置系统初始化进程,即0号进程;*/
sched_init();
preempt_disable(); //禁止抢占
build_all_zonelists();//建立系统内存页区(zone)链表
page_alloc_init();
printk(KERN_NOTICE "Kernelcommand line: %s/n", saved_command_line);
parse_early_param();//解析早期格式的内核参数
parse_args("Bootingkernel", command_line, __start___param,
__stop___param- __start___param,
&unknown_bootoption);//解析新格式内核参数
sort_main_extable();/* 排序内核内建的异常表,将__start___ex_table到__stop___ex_table之间的*(__ex_table)区中的structexception_table_entry型全局结构变量按insn成员变量值从小到大排序,即将可能导致缺页异常的指令按期指令二进制代码值从小到大排序。*/
trap_init();/* Copy thevectors, stubs and kuser helpers (in entry-armv.S)
* intothe vector page, mapped at 0xffff0000, and ensure these
* arevisible to the instruction stream. */
/* * Copy signal return handlers into the vector page, and
* setsigreturn to be a pointer to these. */
rcu_init();
/* *Initializes rcu mechanism. Assumed to be called early.
* Thatis before local timer(SMP) or jiffie timer (uniproc) is setup.
* Notethat rcu_qsctr and friends are implicitly
*initialized due to the choice of ``0'' for RCU_CTR_INVALID. */
init_IRQ();/*中断初始化,初始化系统中支持的最大可能的中断描述结构structirqdesc变量数组irq_desc[NR_IRQS],把每个结构变量irq_desc[n]都初始化为预先定义好的坏中断描述结构变量bad_irq_desc,并初始化该中断的链表表头成员结构变量pend.*/
pidhash_init();/*
* Thepid hash table is scaled according to the amount of memory in the
*machine. From a minimum of 16 slots up to 4096 slots at onegigabyte or
*more.设置系统中每种pidhash表中的hash链表数的移位值全局变量pidhash_shift,将pidhash_shift设置为mini(12);分别为每种hash表的连续hash链表表头结构空间申请内存,把申请到的内存虚拟基址分别传给pid_hash[n](n=0~3),并将每种hash表中的每个hash链表表头结构structhlist_head中的first成员指针设置成NULL.
*/
init_timers();
softirq_init();
time_init();//检查系统定时器描述结构structsys_timer全局变量system_timer是否为空,如果为空将其指向dummy_gettimeoffset()函数。
/*
* HACKALERT! 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();//初始化系统控制台结构,该函数执行后可调用printk()函数将log_buf
//中符合打印级别要求的系统信息打印到控制台上。
if (panic_later)
panic(panic_later,panic_param);
profile_init();//对系统剖析做相关初始化,系统剖析用于系统调用
local_irq_enable(); //使能IRQ
#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) - "
"disablingit./n",initrd_start,min_low_pfn << PAGE_SHIFT);
initrd_start= 0;
}
#endif
vfs_caches_init_early();
mem_init();
//该函数执行完成之后,就不能再用像alloc_bootmem(),alloc_bootmem_low(),alloc_bootmem_pages()等申请低端内存的函数来申请内存,也就不能再申请大块连续的物理内存了。
kmem_cache_init();//slab分配器的相关初始化,执行高速缓存内存管理
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
calibrate_delay();/*
* Thisis the number of bits of precision for the loops_per_jiffy. Each
* bittakes on average 1.5/HZ seconds. This (like the original) is alittle
*better than 1%
*/
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(); //security_init - initializes the security framework
vfs_caches_init(num_physpages);
radix_tree_init();
signals_init();
/*调用函数kmem_cache_create("sigqueue",sizeof(struct sigqueue), __alignof__(struct sigqueue), SLAB_PANIC,NULL, NULL); 为信号队列结构structsigqueque创建高速缓存内存描述结构kmem_cache_t变量,名字叫”sigqueue”,不要求其对象按处理器硬件cacheline大小对齐,没有定义其对象的构造和析构函数,将创建的kmem_cache_t结构变量的地址传给全局指针sigqueue_cachep.*/
/* rootfs populating mightneed page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
//在系统支持proc文件系统即配置了CONFIG_PROC_FS选项时被调用
#endif
cpuset_init();
check_bugs();
acpi_early_init(); /* beforeLAPIC and SMP init */
/* Do the rest non-__init'ed,we're now alive */
rest_init();
/* 创建init()进程,即1号进程,启动调度器,然后系统启动进程即0号进程进入空闲;*/
}
下面来分析几个非常重要的函数:
首先主要分析setup_arch()函数,定义在arch/arm/kernel/setup.c文件中,它完成体系结构相关的初始化,内核移植的过程一般也就到此函数为止了,其余的就只是一些相关的外设驱动。
Setup_arch()中的第一个主要函数为setup_processor(),它只是简单的遍历__proc_info_begin开始的proc_info_list找到相应的表项,并从中得到proc_info结构。注意这些proc_info_list结构定义在arch/arm/mm/proc_arm920.S文件中。
然后就是setup_machine函数,它根据machine_arch_type[机器ID],在__arch_info_begin中找到对应的machine_desc表项。对于FS2410(实际上这里用的是SMDK2410的相关内容,因为二者基本相同,所以在移植的时候没有修改这一部分,用的还是SMDK2410的机器ID)在arch/arm/mach-s3c2410/mach-smdk2410.c中定义为:
MACHINE_START(SMDK2410,"SMDK2410") /* @TODO: request a new identifier and switch
*to SMDK2410 */
/* Maintainer: Jonas Dietsche*/
.phys_ram = S3C2410_SDRAM_PA,
.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 =smdk2410_init_irq,
.init_machine = sdmk2410_init,
.timer = &s3c24xx_timer,
MACHINE_END
接着需要解释的就是u-boot给内核传参数了,linux-2.6.14没有通过u-boot引导时的thekernel的第三个参数传递u-boot传递给内核参数的位置,即boot_params,而是通过指定机器描述结构的mdesc->boot_params来制订u-boot传给内核参数的位置,这时需要先就参数进行分析,由parse_tags(tags);来完成,其中tags= phys_to_virt(mdesc->boot_params);
paging_init(&meminfo,mdesc);是一个非常重要的函数尤其在移植内核时,主要完成页表的初始化。----〉调用memtable_init(mi);初始化内存页表----〉create_mapping(init_maps)创建页目录项荷页表项。
另外在paging_init()函数中还会调用mdesc->map_io();来完成平台相关的IO映射。
接着就是 设置平台相关的指针
init_arch_irq =mdesc->init_irq; //在start_kernel接下来的init_IRQ()函数中会掉用该函数完成平台相关的中断初始化。
system_timer = mdesc->timer;//在start_kernel接下来的time_init()函数中会掉用该结构体中的内容完成平台相关的定时器初始化。
init_machine =mdesc->init_machine;
setup_arch()的分析到此结束。
这里再简单分析一下reset_init()函数:
static void noinlinerest_init(void)
__releases(kernel_lock)
{
kernel_thread(init, NULL,CLONE_FS | CLONE_SIGHAND); //启动init线程
numa_default_policy();
unlock_kernel();
preempt_enable_no_resched();
/* The boot idle thread mustexecute schedule()at least one to get things moving: */
schedule(); //启动调度器
cpu_idle();//cpu空闲,实际上就是启动进程进入空闲状态,系统交给调度器来管理
}
再来看一下启动init线程的过程:
pid_t kernel_thread(int(*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0,sizeof(regs));
regs.ARM_r1 = (unsignedlong)arg;
regs.ARM_r2 = (unsignedlong)fn;
regs.ARM_r3 = (unsignedlong)do_exit;
regs.ARM_pc = (unsignedlong)kernel_thread_helper;
regs.ARM_cpsr = SVC_MODE;
returndo_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
//调用do_fork函数创建init内核线程。
}
接着我们来看一下这个init线程的内容,它是init/main.c中的一个函数,代码如下:
static int init(void *unused)
{
lock_kernel();
set_cpus_allowed(current,CPU_MASK_ALL);
…… …… …… …….
/* Do this before initcalls,because some drivers want to access firmware files. */
populate_rootfs();
do_basic_setup();//很重要的一个函数,做一些基本的设置
…… …… …… …….
/* Ok, we have completed theinitial bootup, and we're essentially up and running. Get rid of theinitmem segments and start the user-mode stuff.. */
free_initmem();
unlock_kernel();
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); //dup文件描述符1
(void) sys_dup(0); //dup文件描述符2
…… …… …… …….
run_init_process("/sbin/init"); //启动init进程
run_init_process("/etc/init"); //启动init进程
run_init_process("/bin/init"); //启动init进程
run_init_process("/bin/sh"); //启动shell
panic("No init found. Try passing init= option to kernel.");
}
/*机器已经初始化完成,cpu子系统已经起来正在运行,内存和进程管理已经工作,接下来要做些真正的工作了 */
static void __initdo_basic_setup(void)
{
/* drivers will send hotplugevents */
init_workqueues(); //初始化工作队列
usermodehelper_init();//用户模式help程序
driver_init(); //设备初始化
#ifdef CONFIG_SYSCTL
sysctl_init();
#endif
/* Networking initializationneeds a process context */
sock_init();
do_initcalls(); //非常重要的一个函数,会在这里执行驱动模块加载函数,也就module_init()的函数。
}
do_initcalls()的一部分代码如下:
static void __initdo_initcalls(void)
{
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start;call < __initcall_end; call++)
{
char *msg;
(*call)();//真正执行驱动模块加载函数的地方
}
最后一个要分析的问题是:关于设备驱动什么时候调用,以网卡驱动为例来进行分析,这里采用的是CS8900A。
驱动里面的模块注册函数module_init(xxx_init);
网卡注册过程:
cs8900_init()---〉register_netdve(&cs8900_dev)---〉register_netdevice()
cs8900_dev定义如下:
int cs8900_probe (structnet_device *dev);
static struct net_devicecs8900_dev =
{
init:cs8900_probe
};
其调用过程:
Register_netdev(structnet_device *dev)----〉register_netdevice(structnet_device * dev)—〉dev->init
这样实际在启动过程中会执行cs8900_probe,里面会对网卡进行侦测,注册中断,DMA中断等一些操作。
而cs8900_init()会在内核启动的时候被执行:
Start_kernel()---〉reset_init()---〉起了init内核线程----〉do_basic_setup()---〉do_initcalls()
--------〉for(call=__initcall_start;call<__initcall_end;call++){
(*call)();《--------------此处会执行cs8900_init,以及其他驱动程序的module_init函数。
}
Include/linux/init.h中关于initcall的定义,这里简单描述,如果感兴趣可以查阅该文件。
#ifndef MODULE
#ifndef __ASSEMBLY__
#define__define_initcall(level,fn) /
staticinitcall_t __initcall_##fn __attribute_used__ /
__attribute__((__section__(".initcall"level ".init"))) = fn
//Initcall定义了7个级别,我们关系的设备initcall位于级别6。
#definecore_initcall(fn) __define_initcall("1",fn)
#definepostcore_initcall(fn) __define_initcall("2",fn)
#definearch_initcall(fn) __define_initcall("3",fn)
#definesubsys_initcall(fn) __define_initcall("4",fn)
#definefs_initcall(fn) __define_initcall("5",fn)
#definedevice_initcall(fn) __define_initcall("6",fn)
#definelate_initcall(fn) __define_initcall("7",fn)
#define__initcall(fn) device_initcall(fn)
#define__exitcall(fn) /
staticexitcall_t __exitcall_##fn __exit_call = fn
#define console_initcall(fn)/
static initcall_t__initcall_##fn /
__attribute_used____attribute__((__section__(".con_initcall.init")))=fn
#define security_initcall(fn)/
static initcall_t__initcall_##fn /
__attribute_used____attribute__((__section__(".security_initcall.init"))) =fn
#define __setup_param(str,unique_id, fn, early) /
static char__setup_str_##unique_id[] __initdata = str; /
static structobs_kernel_param __setup_##unique_id /
__attribute_used__ /
__attribute__((__section__(".init.setup"))) /
__attribute__((aligned((sizeof(long))))) /
={ __setup_str_##unique_id, fn, early }
#define__setup_null_param(str, unique_id) __setup_param(str, unique_id, NULL, 0)
#define __setup(str,fn) __setup_param(str, fn, fn, 0)
#define__obsolete_setup(str) __setup_null_param(str,__LINE__)
#define early_param(str,fn) __setup_param(str, fn, fn, 1)
void __initparse_early_param(void);
#endif /* __ASSEMBLY__ */
#definemodule_init(x) __initcall(x);
#definemodule_exit(x) __exitcall(x);
#else /* MODULE 定义为模块 */
#definecore_initcall(fn) module_init(fn)
#definepostcore_initcall(fn) module_init(fn)
#definearch_initcall(fn) module_init(fn)
#definesubsys_initcall(fn) module_init(fn)
#definefs_initcall(fn) module_init(fn)
#definedevice_initcall(fn) module_init(fn)
#definelate_initcall(fn) module_init(fn)
#definesecurity_initcall(fn) module_init(fn)
#definemodule_init(initfn) /
static inline initcall_t__inittest(void) /
{ return initfn;} /
int init_module(void)__attribute__((alias(#initfn)));
#definemodule_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_null_param(str, unique_id) /* nothing */
#define __setup(str, func) /* nothing */
#define __obsolete_setup(str) /* nothing */
#endif
这篇关于ARM linxu启动过程分析(四)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!