在Linux移植之内核启动过程引导阶段分析中从arch/arm/kernel/head.S开始分析,最后分析到课start_kernel这个C函数,下面就简单分析下这个函数,因为涉及到Linux的内容较多,这里只是简单介绍下内核启动流程。先看一下内核启动的流程框图,截图来自《嵌入式Linux应用开发完全手册》。内核引导阶段已经分析过,接下来分析一下内核启动的第二阶段。
1、start_kernel函数全局概览
2、start_kernel函数调用层次
1、start_kernel函数全局概览,对start_kernel作一下粗略注释。
打开initMain.c ,下面主要分析处理UBOOT传入的参数,其中r1是传入的第一个参数存放的地址,里面存放着机器类型ID,已经处理过了;r2是传入的第二个参数,它存放着tag列表数据的地址。先看一下整个start_kernel函数,以下程序参考自http://www.cnblogs.com/lifexy/p/7366782.html
1 asmlinkage void __init start_kernel(void)
2
3 {
4 char * command_line;
5 extern struct kernel_param __start___param[], __stop___param[];
6
7 smp_setup_processor_id(); //来设置smp process id,当然目前看到的代码里面这里是空的
8
9 unwind_init();
10
11 //lockdep是linux内核的一个调试模块,用来检查内核互斥机制尤其是自旋锁潜在的死锁问题。
12 //自旋锁由于是查询方式等待,不释放处理器,比一般的互斥机制更容易死锁,
13 //故引入lockdep检查以下几种情况可能的死锁(lockdep将有专门的文章详细介绍,在此只是简单列举):
14 //
15 //·同一个进程递归地加锁同一把锁;
16 //
17 //·一把锁既在中断(或中断下半部)使能的情况下执行过加锁操作,
18 // 又在中断(或中断下半部)里执行过加锁操作。这样该锁有可能在锁定时由于中断发生又试图在同一处理器上加锁;
19 //
20 //·加锁后导致依赖图产生成闭环,这是典型的死锁现象。
21 lockdep_init();
22 //关闭当前CUP中断
23 local_irq_disable();
24
25 //修改标记early_boot_irqs_enabled;
26 //通过一个静态全局变量 early_boot_irqs_enabled来帮助我们调试代码,
27 //通过这个标记可以帮助我们知道是否在”early bootup code”,也可以通过这个标志警告是有无效的终端打开
28 early_boot_irqs_off();
29
30 //每一个中断都有一个IRQ描述符(struct irq_desc)来进行描述。
31 //这个函数的主要作用是设置所有的 IRQ描述符(struct irq_desc)的锁是统一的锁,
32 //还是每一个IRQ描述符(struct irq_desc)都有一个小锁。
33 early_init_irq_lock_class();
34 /*
35
36 * Interrupts are still disabled. Do necessary setups, then
37 * enable them
38 */
39 // 大内核锁(BKL--Big Kernel Lock)
40 //大内核锁本质上也是自旋锁,但是它又不同于自旋锁,自旋锁是不可以递归获得锁的,因为那样会导致死锁。
41 //但大内核锁可以递归获得锁。大内核锁用于保护整个内核,而自旋锁用于保护非常特定的某一共享资源。
42 //进程保持大内核锁时可以发生调度,具体实现是:
43 //在执行schedule时,schedule将检查进程是否拥有大内核锁,如果有,它将被释放,以致于其它的进程能够获得该锁,
44 //而当轮到该进程运行时,再让它重新获得大内核锁。注意在保持自旋锁期间是不运行发生调度的。
45 //需要特别指出,整个内核只有一个大内核锁,其实不难理解,内核只有一个,而大内核锁是保护整个内核的,当然有且只有一个就足够了。
46 //还需要特别指出的是,大内核锁是历史遗留,内核中用的非常少,一般保持该锁的时间较长,因此不提倡使用它。
47 //从2.6.11内核起,大内核锁可以通过配置内核使其变得可抢占(自旋锁是不可抢占的),这时它实质上是一个互斥锁,使用信号量实现。
48 //大内核锁的API包括:
49 //
50 //void lock_kernel(void);
51 //
52 //该函数用于得到大内核锁。它可以递归调用而不会导致死锁。
53 //
54 //void unlock_kernel(void);
55 //
56 //该函数用于释放大内核锁。当然必须与lock_kernel配对使用,调用了多少次lock_kernel,就需要调用多少次unlock_kernel。
57 //大内核锁的API使用非常简单,按照以下方式使用就可以了:
58 //lock_kernel(); //对被保护的共享资源的访问 … unlock_kernel();
59 //http://blog.csdn.net/universus/archive/2010/05/25/5623971.aspx
60 lock_kernel();
61
62 //初始化time ticket,时钟
63 tick_init();
64
65 //函数 tick_init() 很简单,调用 clockevents_register_notifier 函数向 clockevents_chain 通知链注册元素:
66 // tick_notifier。这个元素的回调函数指明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,
67 //应该执行的操作,该回调函数为 tick_notify
68 //http://blogold.chinaunix.net/u3/97642/showart_2050200.html
69 boot_cpu_init();
70
71 //初始化页地址,当然对于arm这里是个空函数
72 //http://book.chinaunix.net/special/ebook/PrenticeHall/PrenticeHallPTRTheLinuxKernelPrimer/0131181637/ch08lev1sec5.html
73 page_address_init();
74
75 /*打印KER_NOTICE,这里的KER_NOTICE是字符串<5>*/
76 printk(KERN_NOTICE);
77
78 /*打印以下linux版本信息:
79 “Linux version 2.6.22.6 (book@book-desktop) (gcc version 3.4.5) #1 Fri Jun 16 00:55:53 CST 2017” */
80 printk(linux_banner);
81
82 //系结构相关的内核初始化过程,处理uboot传递进来的atag参数( setup_memory_tags()和setup_commandline _tags() )
83 //http://www.cublog.cn/u3/94690/showart_2238008.html
84 setup_arch(&command_line);
85
86 //处理启动命令,这里就是设置的cmd_line,
87 //保存未改变的comand_line到字符数组static_command_line[] 中。
88 //保存 boot_command_line到字符数组saved_command_line[]中
89 setup_command_line(command_line);
90
91 unwind_setup();
92
93 //如果没有定义CONFIG_SMP宏,则这个函数为空函数。
94 //如果定义了CONFIG_SMP宏,则这个setup_per_cpu_areas()函数给每个CPU分配内存,
95 //并拷贝.data.percpu段的数据。为系统中的每个CPU的per_cpu变量申请空间。
96 setup_per_cpu_areas();
97
98 //定义在include/asm-x86/smp.h。
99 //如果是SMP环境,则设置boot CPU的一些数据。在引导过程中使用的CPU称为boot CPU
100 smp_prepare_boot_cpu();
101
102 /* arch-specific boot-cpu hooks */
103 /* 进程调度器初始化 */
104 sched_init();
105
106 /* 禁止内核抢占 */
107 preempt_disable();
108
109 //设置node 和 zone 数据结构
110 //内存管理的讲解:http://blog.chinaunix.net/space.php?uid=361890&do=blog&cuid=2146541
111 build_all_zonelists(NULL);
112
113 //初始化page allocation相关结构
114 page_alloc_init();
115
116 /* 打印Linux启动命令行参数 */
117 printk(KERN_NOTICE 'Kernel command line: %s/n', boot_command_line);
118
119 //解析内核参数
120 //对内核参数的解析:http://hi.baidu.com/yuhuntero/blog/item/654a7411e45ce519b8127ba9.html
121 parse_early_param();
122 parse_args('Booting kernel', static_command_line, __start___param,
123 __stop___param - __start___param,
124 &unknown_bootoption);
125
126 /*
127 * These use large bootmem allocations and must precede
128 * kmem_cache_init()
129 */
130 //初始化hash表,以便于从进程的PID获得对应的进程描述指针,按照实际的物理内存初始化pid hash表
131 //这里涉及到进程管理http://blog.csdn.net/satanwxd/archive/2010/03/27/5422053.aspx
132 pidhash_init();
133
134 //初始化VFS的两个重要数据结构dcache和inode的缓存。
135 //http://blog.csdn.net/yunsongice/archive/2011/02/01/6171324.aspx
136 vfs_caches_init_early();
137
138 //把编译期间,kbuild设置的异常表,也就是__start___ex_table和__stop___ex_table之中的所有元素进行排序
139 sort_main_extable();
140
141 //初始化中断向量表
142 //http://blog.csdn.net/yunsongice/archive/2011/02/01/6171325.aspx
143 trap_init();
144
145 //memory map初始化
146 //http://blog.csdn.net/huyugv_830913/archive/2010/09/15/5886970.aspx
147 mm_init();
148
149 /*
150 * Set up the scheduler prior starting any interrupts (such as the
151 * timer interrupt). Full topology setup happens at smp_init()
152 * time - but meanwhile we still have a functioning scheduler.
153 */
154 //核心进程调度器初始化,调度器的初始化的优先级要高于任何中断的建立,
155 //并且初始化进程0,即idle进程,但是并没有设置idle进程的NEED_RESCHED标志,
156 //所以还会继续完成内核初始化剩下的事情。
157 //这里仅仅为进程调度程序的执行做准备。
158 //它所做的具体工作是调用init_bh函数(kernel/softirq.c)把timer,tqueue,immediate三个人物队列加入下半部分的数组
159 sched_init();
160
161 /*
162 * Disable preemption - early bootup scheduling is extremely
163 * fragile until we cpu_idle() for the first time.
164 */
165 //抢占计数器加1
166 preempt_disable();
167
168 //检查中断是否打开,如果已经打开,则关闭中断
169 if (!irqs_disabled()) {
170 printk(KERN_WARNING 'start_kernel(): bug: interrupts were '
171 'enabled *very* early, fixing it/n');
172 local_irq_disable();
173 }
174
175 sort_main_extable();
176 /*
177 * trap_init函数完成对系统保留中断向量(异常、非屏蔽中断以及系统调用)
178 * 的初始化,init_IRQ函数则完成其余中断向量的初始化
179 */
180 trap_init();
181
182 //Read-Copy-Update的初始化
183 //RCU机制是Linux2.6之后提供的一种数据一致性访问的机制,
184 //从RCU(read-copy-update)的名称上看,我们就能对他的实现机制有一个大概的了解,
185 //在修改数据的时候,首先需要读取数据,然后生成一个副本,对副本进行修改,
186 //修改完成之后再将老数据update成新的数据,此所谓RCU。
187 //http://blog.ednchina.com/tiloog/193361/message.aspx
188 //http://blogold.chinaunix.net/u1/51562/showart_1341707.html
189 rcu_init();
190
191 //初始化IRQ中断和终端描述符。
192 //初始化系统中支持的最大可能的中断描述结构struct irqdesc变量数组irq_desc[NR_IRQS],
193 //把每个结构变量irq_desc[n]都初始化为预先定义好的坏中断描述结构变量bad_irq_desc,
194 //并初始化该中断的链表表头成员结构变量pend
195 init_IRQ();
196
197 /* 初始化hash表,便于从进程的PID获得对应的进程描述符指针 */
198 pidhash_init();
199
200 //初始化定时器Timer相关的数据结构
201 //http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html
202 init_timers();
203
204 //对高精度时钟进行初始化
205 hrtimers_init();
206
207 //软中断初始化
208 //http://blogold.chinaunix.net/u1/51562/showart_494363.html
209 softirq_init();
210
211 //初始化时钟源
212 timekeeping_init();
213
214 //初始化系统时间,
215 //检查系统定时器描述结构struct sys_timer全局变量system_timer是否为空,
216 //如果为空将其指向dummy_gettimeoffset()函数。
217 //http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html
218 time_init();
219
220 //profile只是内核的一个调试性能的工具,
221 //这个可以通过menuconfig中的Instrumentation Support->profile打开。
222 //http://www.linuxdiyf.com/bbs//thread-71446-1-1.html
223 profile_init();