内核启动流程2——C语言部分

注:本文是学习朱老师课程整理的笔记,基于linux2.6.35.7和九鼎X210BV3S开发板进行移植。

C语言部分的代码是与硬件结构无关的代码。从start_kernel函数开始到第一个用户进程init结束,过程中调用了一系列的初始化的函数对所有的内核组件进行初始化。其中,start_kernel、rest_init 、kernel_init、init_post等4个函数构成了整个初始化过程的主线,如下图所示:

打印内核信息

在kernel/init/main.c中:
572  printk(KERN_NOTICE "%s", linux_banner);

printk函数是内核中用来从console打印信息的,类似于应用层编程中的printf。printk函数的用法和printf几乎一样,不同之处在于可以在参数最前面用一个宏来定义消息输出的级别。为什么要有这种级别?主要原因是linux内核太大了,代码量太多,里面的printk打印信息太多了。如果所有的printk都能打印出来而不加任何限制,则最终内核启动后得到海量的输出信息。为了解决打印信息过多,无效信息会淹没有效信息这个问题,linux内核的解决方案是给每一个printk添加一个打印级别。级别定义0-7(编程的时候要用相应的宏定义)分别代表8种输出的重要性级别,0表示最重要,7表示最不重要。我们在printk的时候自己根据自己的消息的重要性去设置打印级别。

linux的控制台监测消息的地方也有一个消息过滤显示机制,控制台实际只会显示级别比我的控制台定义的级别高的消息。譬如说控制台的消息显示级别设置为4,那么只有printk中消息级别为0-3的才可以显示看见,其余的被过滤掉了。

setup_arch

对体系架构进行初始化,再次获取CPU类型和机器类型、分析引导加载程序(如uboot)传入的命令行参数、进行页面内存和处理器初始化、中断早期初始化等等。

setup_arch对cmdline的处理思路是:内核中自己维护了一个默认的cmdline,然后uboot还可以通过tag给kernel再传递一个cmdline。如果uboot给内核传cmdline成功则内核会优先使用uboot传递的这一个;如果uboot没有给内核传cmdline或者传参失败,则内核会使用自己默认的这个cmdline。


parse_args

解析cmdline传参和其他传参。把cmdline的细节设置信息给解析出来。譬如cmdline:console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3,则解析出的内容就是就是一个字符串数组,数组中依次存放了一个设置项目信息。这里只是进行了解析,并没有去处理。

rest_init

在start_kernel()已经对基本的硬件、系统的结构初始化完成。在内核初始化函数start_kernel()执行到最后,调用rest_init()函数,该函数主要用于创建并启动内核线程init。

总结:start_kernel函数做的主要工作:打印了一些信息、内核工作需要的模块的初始化(譬如内存管理、调度系统、异常处理···)、我们需要重点了解的就是setup_arch中做的2件事情:机器码架构的查找并且执行架构相关的硬件的初始化、uboot给内核的传参cmdline。
440      kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
441      numa_default_policy();
442      pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

455      schedule();
459      cpu_idle();

rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthread。

kthread的作用是管理和调度其他内核线程。内核循环执行kthread函数,该函数的作用是运行kthread_create_list全局链表中维护的内核线程。调用kthread_create创建一个kthread,它会被加入kthread_create_list 链表中;执行过的kthread从kthread_create_list 链表中删除;kthread不断调用schedule()函数让出CPU。schedule()用来执行调度,切换进程,从此linux系统开始转起来了。此线程不可关闭。

上面两个线程就是我们平时在Linux系统中用ps命令看到:
ubuntu@VM-199-159-ubuntu:/work/kernel$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.2   4292  2204 ?        Ss   Oct30   0:02 /sbin/init
root         2  0.0  0.0      0     0 ?        S    Oct30   0:00 [kthreadd]


cpu_idle()

内核执行到cpu_idle()进入到IDLE状态,用循环消耗空闲的CPU时间片。该函数从不返回,而是在while(1)循环中不断检查是否有其他进程需要调度,如果有,该进程就被抢占从而让出CPU。


kernel_init()

在rest_init()函数中,内核创建两个线程,一个是内核的线程管理者线程kthread,另一个是内核初始化线程init,后者是分析内核启动需要关注的。init进程的执行函数是kernel_init() ,init线程在该函数中继续完成系统的初始化。

init_post()

在内核init线程的最后执行init_post()函数,在该函数中真正启动用户空间的init进程。

一个进程2种状态。init进程刚开始运行的时候是内核态,它属于一个内核线程,然后他自己运行了一个用户态下面的程序后把自己强行转成了用户态。因为init进程自身完成了从内核态到用户态的过度,因此后续的其他进程都可以工作在用户态下面了。

内核态下做了什么?重点就做了一件事情,就是挂载根文件系统并试图找到用户态下的那个init程序。init进程要把自己转成用户态就必须运行一个用户态的应用程序(这个应用程序名字一般也叫init),要运行这个应用程序就必须得找到这个应用程序,要找到它就必须得挂载根文件系统,因为所有的应用程序都在文件系统中。

init进程在内核态下面时,通过一个函数kernel_execve来执行一个用户空间编译连接的应用程序就跳跃到用户态了。注意这个跳跃过程中进程号是没有改变的,所以一直是进程1。这个跳跃过程是单向的,也就是说一旦执行了init程序转到了用户态下以后只能在用户态下工作了,用户态下想要进入内核态只有走API这一条路了。

prepare_namespace函数中挂载根文件系统。 uboot通过传参来告诉内核根文件系统的基本信息。
uboot传参中的root=/dev/mmcblk0p2 rw 这一句就是告诉内核根文件系统在mmc的第0块,第2个分区,且可读写;
uboot传参中的rootfstype=ext3这一句就是告诉内核rootfs的文件类型是ext3。

如果内核挂载根文件系统成功,则会打印出:VFS: Mounted root (ext3 filesystem) on device 179:2.
如果挂载根文件系统失败,则会打印:No filesystem could mount root, tried:  yaffs2

如果挂载rootfs失败,可能的原因有:
  1. 最常见的错误就是uboot的bootargs设置不对。
  2. 文件系统挂载有问题,文件不存在。
  3. init程序没有执行权限。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值