Linux 2.6.28 – 内核启动分析(一)
首先声明,我也是初学Linux内核,因为有这方面的基础,所以打算学习一下内核,学习Linux要讲究方法,坚持固然重要,但是也要讲究方法,我个人认为,学习内核是一个长期的过程,如果指望一两天或者一两个星期就行弄清内核(除了你很杰出+有学习内核的天赋),那么我想说,你不适合学习Linux内核,所谓方法,就是分清主次矛盾,分清主干和枝叶,这样才能快速的入门。
下面我就从Linux的“main函数”开始我的Linux的内核之旅,所谓Linux的“main函数”,就是指从这个函数开始进入了和硬件体系结构无关的内核部分,这也就意味着进入了内核的初始化过程,Linux的内核初始化过程由start_kernel开始,直到第一个用户进程init结束,调用了一系列的初始化函数,其中每一步都将我们带入更大量、更复杂的代码和结构中!如果我们不分清主干和枝叶的学习,那么我们根本无法理解内核的原理!即使你坚持下来了,那么你也只是好好好好好….. 好好好好好的学习了一下C语言!因此,我的学习主线是:
Start_kernel(); –> rest_ini(); -> kernel_init(); -> init_post(); ….. 接下来我们就开始我Linux内核分析之旅!
asmlinkage void __init start_kernel(void)
{
char * command_line;
---------------------------------------------------------------------------------------------------------------------------------
这两个外部变量,是内核编译剧本定位的内核参数肇端地址
extern struct kernel_param __start___param[], __stop___param[];
---------------------------------------------------------------------------------------------------------------------------------
指定当前的cpu的逻辑号,这个函数对应于对称多处理器的设置,当系统中只有一个cpu的情况,此函数为空,什么也不做
smp_setup_processor_id();
---------------------------------------------------------------------------------------------------------------------------------
unwind_init();
---------------------------------------------------------------------------------------------------------------------------------
初始化内核依赖的关系表。一些体系结构拥有自己的start_kernel()代码回去调用lockdep_init(),与此同时也会从start_kernel()中调用lockdep_init()仅仅是为了初始化hash表
lockdep_init();
---------------------------------------------------------------------------------------------------------------------------------
debug_objects_early_init();
cgroup_init_early();
---------------------------------------------------------------------------------------------------------------------------------
关闭当前CUP中断
local_irq_disable();
---------------------------------------------------------------------------------------------------------------------------------
修改标记early_boot_irqs_enabled;通过一个静态全局变量 early_boot_irqs_enabled来帮助我们调试代码,通过这个标记可以帮助我们知道是否在”early bootup code”,也可以通过这个标志警告是有无效的终端打开
early_boot_irqs_off();
---------------------------------------------------------------------------------------------------------------------------------
每一个中断都有一个IRQ描述符(struct irq_desc)来进行描述。这个函数的主要作用是设置所有的 IRQ描述符(struct irq_desc)的锁是统一的锁,还是每一个IRQ描述符(struct irq_desc)都有一个小锁。
early_init_irq_lock_class();
---------------------------------------------------------------------------------------------------------------------------------
获得大内核锁,用于锁定整个内核,大内核锁的上锁其实就是对当前进程的lock_depth加1,并判断加1后,如果等于0则为第一次上锁,需对当前结构的thread_info的preempt_count也加1阻止抢占,大内核锁的特点之一就是可以递归调用,也即可以重复上锁,只要解锁次数与上锁次数相同即可。实现方式就是lock_depth值累计上锁次数,但preempt_count值并不需累计,所以同一进程的第二次上锁时只对lock_depth累计而不对preempt_count累计。
lock_kernel();
---------------------------------------------------------------------------------------------------------------------------------
如果没有定义 CONFIG_GENERIC_CLOCKEVENTS宏定义,则这个函数为空函数,如果定义了这个宏,这执行初始化 tick控制功能,注册clockevents的框架。
tick_init();
---------------------------------------------------------------------------------------------------------------------------------
对于CPU核的系统来说,设置第一个CPU核为活跃 CPU核。对于单CPU核系统来说,设置CPU核为活跃 CPU核
boot_cpu_init();
---------------------------------------------------------------------------------------------------------------------------------
当定义了CONFIG_HIGHMEM 宏,并且没有定义 WANT_PAGE_VIRTUAL 宏时,非空函数。其他情况为空函数。注意:ARM9不支持高端地址(大于896M),一般的嵌入式产品也不会用高端地址,所以,在ARM体系结构下,此函数为空!
page_address_init();
---------------------------------------------------------------------------------------------------------------------------------
输出打印版本信息
printk(KERN_NOTICE);
printk(linux_banner);
---------------------------------------------------------------------------------------------------------------------------------
每种体系结构都有自己的 setup_arch()函数,这些是体系结构相关的。如何确定编译那个体系结构的setup_arch()函数呢?
主要由linux源码树顶层Makefile中ARCH变量来决定的。
例如: ARM体系结构的。
SUBARCH :=arm
ARCH ?= $(SUBARCH)
该函数在所在的路劲为 /arm/kernel/setup.c,其中那个command_line就是有bootloader传过来的!这个函数是个重量级的函数,大家不可忽视!该函数完成体系结构相关的初始化,内核移植的过程一般也就到此函数为止了,其余的就只是一些相关的外设驱动。
setup_arch(&command_line);
---------------------------------------------------------------------------------------------------------------------------------
mm_init_owner(&init_mm, &init_task);
---------------------------------------------------------------------------------------------------------------------------------
保存未改变的comand_line到字符数组static_command_line[] 中。保存 boot_command_line到字符数组saved_command_line[]中
setup_command_line(command_line);
---------------------------------------------------------------------------------------------------------------------------------
空函数
unwind_setup();
---------------------------------------------------------------------------------------------------------------------------------
如果没有定义CONFIG_SMP宏,则这个函数为空函数。如果定义了CONFIG_SMP宏,则这个setup_per_cpu_areas()函数给每个CPU分配内存,并拷贝.data.percpu段的数据。为系统中的每个CPU的per_cpu变量申请空间。
setup_per_cpu_areas();
---------------------------------------------------------------------------------------------------------------------------------
setup_nr_cpu_ids();
---------------------------------------------------------------------------------------------------------------------------------
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.
*/
---------------------------------------------------------------------------------------------------------------------------------
核心进程调度器初始化,调度器的初始化的优先级要高于任何中断的建立,并且初始化进程0,即idle进程,但是并没有设置idle进程的NEED_RESCHED标志,所以还会继续完成内核初始化剩下的事情。
sched_init();
---------------------------------------------------------------------------------------------------------------------------------
进制内核的抢占。使当前进程的 struct thread_info结构 preempt_count成员的值增加1。
preempt_disable();
---------------------------------------------------------------------------------------------------------------------------------
建立系统内存页区(zone)链表
build_all_zonelists();
--------------------------------------------------------------------------------------------------------------------------------
page_alloc_init();
--------------------------------------------------------------------------------------------------------------------------------
打印Linux启动命令行参数
printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line);
--------------------------------------------------------------------------------------------------------------------------------
解析早期格式的内核参数
parse_early_param();
--------------------------------------------------------------------------------------------------------------------------------
该函数对Linux启动命令行参数进行在分析和处理,__stop___param - __start___param这两个参数所定义的位置在 /arch/arm/kernel/vmlinux.lds中定义,最后的那参数的作用是,当不能够识别前面的命令时,所调用的函数。
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
--------------------------------------------------------------------------------------------------------------------------------
先检查中断是否已经打开,若打开,输出信息后则关闭中断。
if (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it/n");
local_irq_disable();
}
--------------------------------------------------------------------------------------------------------------------------------对内核建立的异常处理向量表进行堆排序。
sort_main_extable();
--------------------------------------------------------------------------------------------------------------------------------
设置CPU的异常处理函数。
trap_init();
--------------------------------------------------------------------------------------------------------------------------------
rcu_init();
--------------------------------------------------------------------------------------------------------------------------------
初始化IRQ中断和终端描述符。初始化系统中支持的最大可能的中断描述结构struct irqdesc变量数组irq_desc[NR_IRQS],把每个结构变量irq_desc[n]都初始化为预先定义好的坏中断描述结构变量bad_irq_desc,并初始化该中断的链表表头成员结构变量pend.
init_IRQ();
--------------------------------------------------------------------------------------------------------------------------------
初始化hash表,以便于从进程的PID获得对应的进程描述指针,按照开发办上的物理内存初始化pid hash表
pidhash_init();
--------------------------------------------------------------------------------------------------------------------------------
初始化定时器Timer相关的数据结构。
init_timers();
--------------------------------------------------------------------------------------------------------------------------------
对高精度时钟进行初始化。
hrtimers_init();
--------------------------------------------------------------------------------------------------------------------------------
初始化软中断。
softirq_init();
--------------------------------------------------------------------------------------------------------------------------------
初始化资源和普通计时器
timekeeping_init();
--------------------------------------------------------------------------------------------------------------------------------
初始化系统时间,检查系统定时器描述结构struct sys_timer全局变量system_timer是否为空,如果为空将其指向dummy_gettimeoffset()函数。
time_init();
--------------------------------------------------------------------------------------------------------------------------------
sched_clock_init();
--------------------------------------------------------------------------------------------------------------------------------
对内核的一个性能测试工具profile进行初始化。
profile_init();
--------------------------------------------------------------------------------------------------------------------------------
if (!irqs_disabled())
printk("start_kernel(): bug: interrupts were enabled early/n");
--------------------------------------------------------------------------------------------------------------------------------
early_boot_irqs_on();
--------------------------------------------------------------------------------------------------------------------------------
使能IRQ中断
local_irq_enable();
--------------------------------------------------------------------------------------------------------------------------------
初始化控制台以显示printk的内容,在此之前调用的printk ,只是把数据存到缓冲区里,只有在这个函数调用后,才会在控制台打印出内容!该函数执行后可调用printk()函数将log_buf中符合打印级别要求的系统信息打印到控制台上。
console_init();
--------------------------------------------------------------------------------------------------------------------------------
if (panic_later)
panic(panic_later, panic_param);
--------------------------------------------------------------------------------------------------------------------------------
如果定义了CONFIG_LOCKDEP宏,那么就打印锁依赖信息,否则什么也不做
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();
转载的