指定init启动Linux内核,初看linux内核启动过程

本周的博客依旧依托于一个实验来展开,即跟踪调试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

0818b9ca8b590ca3270a3433284dd417.png

接下来,再打开一个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表表示的是一个全局的锁,锁链表,这里做一下初始化。不细究。

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_MAGIC0x57AC6E9D继续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中会打印如下图所示,也就是初始化控制台。

0818b9ca8b590ca3270a3433284dd417.png

接下来还有 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_init_process就是通过execve()来运行init程序。这里首先运行“/sbin/init”,如果失败再运行“/etc/init”,然后是“/bin/init”,然后是“/bin/sh”(也就是说,init可执行文件可以放在上面代码中寻找的4个目录中都可以),如果都失败,则可以通过在系统启动时在添加的启动参数来指定init,比如init="/home/init"自己定义。

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

0818b9ca8b590ca3270a3433284dd417.png

总结: 从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、付费专栏及课程。

余额充值