初看linux内核启动过程

罗晓波+ 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

本周的博客依旧依托于一个实验来展开,即跟踪调试linux内核启动的过程,着重分析一下从start_kernel函数开始到init进程开始执行的过程。如有理解不到位地方,望批评指正。

实验环境依旧采用实验楼,http://www.shiyanlou.com/courses/195 。

首先,用下面的命令来冰冻系统启动,进而可以开始一步一步跟踪调试代码,看看每一步发生了什么。并如下图。

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

接下来,再打开一个shell,用以下命令配置一下gdb,就可以开始调试了。

gdb
(gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
在start_kernel处设置断点,然后开始continue。

来到第一个函数:lockdep_init() ,这个函数官方给的文档是,尽早执行这个函数并且只初始化一次,所以它作为内核启动函数的第一个调用函数,那么,到底它是干嘛的呢?观其代码,原来是初始化一张hash表,这张hash表又是什么样的存在呢?

void lockdep_init(void)
{
	int i;

	/*
	 * Some architectures have their own start_kernel()
	 * code which calls lockdep_init(), while we also
	 * call lockdep_init() from the start_kernel() itself,
	 * and we want to initialize the hashes only once:
	 */
	if (lockdep_initialized)
		return;

	for (i = 0; i < CLASSHASH_SIZE; i++)
		INIT_LIST_HEAD(classhash_table + i);

	for (i = 0; i < CHAINHASH_SIZE; i++)
		INIT_LIST_HEAD(chainhash_table + i);

	lockdep_initialized = 1;
}
这张hash表表示的是一个全局的锁,锁链表,这里做一下初始化。不细究。

继续next,set_task_stack_end_magic(&init_task);

void set_task_stack_end_magic(struct task_struct *tsk)
{
	unsigned long *stackend;

	stackend = end_of_stack(tsk);
	*stackend = STACK_END_MAGIC;	/* for overflow detection */
}
这是将一个0号进程的栈底处,将一个魔数写入,由其注释可以知道,为了防止overflow,这个魔数是

#define STACK_END_MAGIC		0x57AC6E9D
继续next,下面着重看一些我能看得懂的函数吧,其他的就略过了。。
smp_setup_processor_id();//针对SMP处理器,如果不是,则是弱引用函数
debug_objects_early_init();//初始化debug kernel相关
cgroup_init_early()
这个函数,主要是初始化cgroup中的数据结构,cgroup是一组进程的行为控制。

trap_init();这个函数主要是初始化内核的一些中断向量,截取其中部分代码:

	set_intr_gate(X86_TRAP_DE, divide_error);
	set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
	/* int4 can be called from all */
	set_system_intr_gate(X86_TRAP_OF, &overflow);
	set_intr_gate(X86_TRAP_BR, bounds);
	set_intr_gate(X86_TRAP_UD, invalid_op);
	set_intr_gate(X86_TRAP_NM, device_not_available);
#ifdef CONFIG_X86_32
	set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
#else
	set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
#endif
	set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);
	set_intr_gate(X86_TRAP_TS, invalid_TSS);
	set_intr_gate(X86_TRAP_NP, segment_not_present);
	set_intr_gate(X86_TRAP_SS, stack_segment);
	set_intr_gate(X86_TRAP_GP, general_protection);
	set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);
	set_intr_gate(X86_TRAP_MF, coprocessor_error);
	set_intr_gate(X86_TRAP_AC, alignment_check);

mm_init()
这是一个内核内存管理的初始化函数,其中,这个在main.c同文件中的mm_init()函数中包括的 mem_init();就初始化了内存中的信息。
sched_init(),这个函数初始化进程调度器,并初始化调度队列。
紧接着sched_init()是preempt_disable();是禁用抢占和中断,在内核启动的早期,调度是极其脆弱的。
在跟踪到 console_init()时候,qemu中会打印如下图所示,也就是初始化控制台。



接下来还有 key_init(),内核密钥管理系统初始化;security_init();内核安全框架初始化等等。接下来重点分析一下rest_init(),这个函数主要为了创建第一个init进程。

static noinline void __init_refok rest_init(void)
{
	int pid;

	rcu_scheduler_starting();
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */
	kernel_thread(kernel_init, NULL, CLONE_FS);
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();
	complete(&kthreadd_done);

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	init_idle_bootup_task(current);
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE);
}
看一下kernel_thread(kernel_init, NULL, CLONE_FS);  这个kernel_init是个函数指针,kernel_thread 启动一个内核态线程,

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
	return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
		(unsigned long)arg, NULL, NULL);
}
可以看到这里这个kernel_thread dofork了一个新的进程,再跟踪到这个函数指针fn中,也就是这个kernel_init中,step into 到这个kernel_init里面,可以观察到这么一段代码:

	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;
try_to_run_in<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">it_process就是通过execve()来运行init程序。这里首先运行“/sbin/init”,如果失败再运行“/etc/init”,然后是“/bin/init”,然后是“/bin/sh”(也就是说,init可执行文件可以放在上面代码中寻找的4个目录中都可以),如果都失败,则可以通过在系统启动时在添加的启动参数来指定init,比如init="/home/init"自己定义。</span>

在这个过程中,可以看到产生了两个进程,即init用户态进程以及kthreadd进程,kthread的pid为2。init的pid为1。下图为qemu启动之后的menu小系统的样纸。



总结:
从start_kernel开始到结束的过程就是整个内核初始化的过程,在初始化之前,那个后来沦为idle进程的也就是内核静态创建的一个pcb,也就是0号进程,通过rest_init进一步fork出了两个进程,一个是init进程,另外一个是kthread进程。当用户态进程启动之后,也就是没idle进程什么事了~





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值