点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
__primary_switched最后义无反顾的跳转到了start_kernel(init/main.c)。为什么说义无反顾?因为使用的是b汇编指令而不是bl汇编指令,代表着跳转之后压根没有回头路。而且,前面已经构造好了栈空间,已经可以放心的执行C函数start_kernel。
盘古开天辟地开始了,创世纪开始了,start_kernel实际上开始进行各种子系统的初始化。具体初始化过程有很多文章和书籍都有分析,就不一一赘述了。重点关注和中断相关的初始化。
第542行,关闭中断。实际上从内核运行开始一直都是关中断的,此处算是一个双保险操作。因为I-pipe要区分物理中断和虚拟中断,对此处会根据需要进行修改,后面会详细分析。
第590行,trap_init()定义在arch/arm64/kernel/traps.c,它的主要作用是:启用BRK指令为BUG()服务,在commit 9fb7410f955f7引入。__ipipe_init_early在trap_init()之前调用。
第637行,early_irq_init()定义在kernel/irq/irqdesc.c,初始化预分配的中断preallocated irqs。对于ARM64,dmesg启动过程中打印preallocated irqs为0,所以判断针对ARM64没有实质内容。
[ 0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
第638行,init_IRQ()真正进行ARM64中断初始化。
start_kernel
-> init_IRQ
-> irqchip_init
-> of_irq_init(__irqchip_of_table)
-> gic_of_init
-> gic_init_bases
-> set_handle_irq(gic_handle_irq)
第645行,time_init()对时钟进行初始化。__ipipe_init()的实质初始化,就放在了中断和时间子系统初始化之后。
第657行,打开中断。I-pipe会对local_irq_enable()进行改造,把它的语义从打开物理中断变成打开虚拟中断。
盘古开天辟地的最后一步,就是分出天和地,start_kernel的最后一步是rest_init,也有异曲同工之妙。对代码进行精简,只保留最核心的部分:
static noinline void __ref rest_init(void)
{
……
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
……
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
……
cpu_startup_entry(CPUHP_ONLINE);
}
1号进程是0号进程调用kernel_thread->_do_fork创建的,本身是一个内核进程。1号进程的入口函数是kernel_init,它在当前根目录下寻找init程序,通过execve执行init程序转到用户态,变身为用户进程。
此时的init程序实际上来自于initramfs或initrd,为方便描述,简称oldroot/init。它的职责是从initramfs或initrd中加载驱动并找到真正的根文件系统newroot,调用switch_root切换到newroot,并执行newroot/init程序。最重要的点是,newroot/init的进程号依然是1,这是怎么做到的?从switch_root源码可知,它复用了原来的1号进程,调用execv(init, initargs)重新装载newroot/init程序并执行,可以说是偷梁换柱的高手。当前,在嵌入式Linux中,经常把initramfs或initrd之间当作根文件系统,原理就是把oldroot/init的职责变更了,不再寻找newroot了,反而自己当家作主了,有点鸠占鹊巢的意思。
Linux操作系统先后出现2个主流的init程序:System-V init和systemd。嵌入式Linux很多还在用System-V init,但是主流Linux发行版(例如红帽、Ubuntu)都在使用systemd.
总而言之,1号进程init是所有用户进程的祖先!这里把它当作"地进程"!
0 号进程在初始化阶段,无论是变量名还是函数名都使用了init前缀或后缀(例如init_task/init_stack等),而1号进程竟然也叫init进程,要特别注意不要混淆。
2号进程是0号进程调用kernel_thread->_do_fork创建的,是一个内核进程,入口函数是kthreadd,它利用循环不断地判断kthread_create_list链表节点是否不为空,只要不为空,就取出链表节点,调用create_kthread-> kernel_thread->_do_fork创建相应的内核线程,然后删除链表节点。
内核提供kthread_create/kthread_run来创建内核线程,实际上是向kthread_create_list链表添加新的链表节点,然后等待2号进程完成内核线程的创建。
总而言之,2号进程kthreadd也是今后所有内核进程的祖先!这里把它当作”天进程“!
当走到rest_init->cpu_startup_entry,意味着0号进程swapper已经完成了自己的使命,最终循环调用do_idle函数。只有当这个CPU上没有其它可运行的进程时,调度器才会选择执行进程0,让CPU进入空闲状态。0号进程因此有个绰号叫做idle进程。
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!