linux 内核移植(七)——rest_init函数分析

简介:

代码在start_kernel函数运行的最后到了rest_init()函数中


1:rest_init()函数分析

  (1)rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd

  (2)调用schedule函数开启了内核的调度系统,从此linux系统开始转起来了。

  (3)rest_init最终调用cpu_idle函数结束了整个内核的启动。也就是说linux内核最终结束了一个函数cpu_idle。这个函数里面肯定是死循环。

  (4)简单来说,linux内核最终的状态是:有事干的时候去执行有意义的工作(执行各个进程任务),实在没活干的时候就去死循环(实际上死循环也可以看成是一个任务)。

  (5)之前已经启动了内核调度系统,调度系统会负责考评系统中所有的进程,这些进程里面只有有哪个需要被运行,调度系统就会终止cpu_idle死循环进程(空闲进程)转而去执行有意义的干活的进程。这样操作系统就转起来了。


2.1:什么是内核线程

  (1)进程和线程。简单来理解,一个运行的程序就是一个进程。所以进程就是任务、进程就是一个独立的程序。

独立的意思就是这个程序和别的程序是分开的,这个程序可以被内核单独调用执行或者暂停。

  (2)在linux系统中,线程和进程非常相似,几乎可以看成是一样的。实际上我们当前讲课用到的进程和线程的概念就是

一样的。

  (3)进程/线程就是一个独立的程序。应用层运行一个程序就构成一个用户进程/线程,那么内核中运行

一个函数(函数其实就是一个程序)就构成了一个内核进程/线程。

  (4)所以我们kernel_thead函数运行一个函数,其实就是把这个函数变成了一个内核线程去运行起来,然后他可以被内核调度系统去调度。说白了就是去调度器注册了一下,以后人家调度的时候会考虑你。


2.2:进程0、进程1、进程2

 (1)操作系统是用一个数字来表示/记录一个进程/线程的,这个数字就被称为这个进程的进程号。这个号码是从0开始分配的。因此这里涉及到的三个进程分别是linux系统的进程0、进程1、进程2.

 (2)在linux命令行下,使用ps命令可以查看当前linux系统中运行的进程情况。

(4)我们在ubuntu下ps -aux可以看到当前系统运行的所有进程,可以看出进程号是从1开始的。为什么不从0开始,因为进程0不是一个用户进程,而属于内核进程。

  进程0:进程0其实就是刚才讲过的idle进程,叫空闲进程,也就是死循环。

  进程1:kernel_init函数就是进程1,这个进程被称为init进程。

  进程2:kthreadd函数就是进程2,这个进程是linux内核的守护进程。它的作用是管理调度其他内核进程这个进程是用来保证linux内核自己本身能正常工作的。


3:init进程分析

  需要注意的一点是这个进程刚开始运行的时候是内核态,是属于内核进程,然后它自己运行了一个用户太下面的程序后把自己强行转成了用户态,因为init进程自身完成了从内核态到用户态的过渡,所以后续的其他进程都可以工作在用户态下面了


3.1:init进程在内核态下做了什么

  重要的点就挂载根文件系统,并试图找到用户态下的那个init程序,原因是init进程要完成从内核态到用户态的转变就必须去运行一个用户态的应用程序,而内核源代码中的程序都是属于内核态的,所以这个应用程序必须不属于内核源代码,这样才能保证自己是用户态,所以这个应用程序就的是由另外一份文件提供,即根文件系统

3.2: init进程在用户态下做了什么

  init进程大部分有意义的工作都是在用户态下进行的,原因是用户态下的所有进程都是直接或者间接由init进程生成的。

3.3:如何从内核态跳跃到用户态?还能回来不?

  init进程在内核态下面时,通过调用kernel_execve函数来执行一个用户空间编译链接的应用程序就跳跃到了用户态下面了,需要注意的是,这个跳跃的过程进程号并没有改变还是进程1,并且这个跳跃是单向的,以后要从用户态回到内核态只有走API这一条路了

kernel_execve函数被调用的路径start_kernel->rest_init->kernel_thread->kernel_init->init_post->run_init_process->kernel_execve


4:init进程在内核态下的分析(也就是kernel_init函数)

4.1:打开控制台,代码如下:

1
2
3
4
5
/* Open the /dev/console on the rootfs, this should never fail */
if  (sys_open(( const  char  __user *)  "/dev/console" , O_RDWR, 0) < 0)
printk(KERN_WARNING  "Warning: unable to open an initial console.\n" );
( void ) sys_dup(0);
( void ) sys_dup(0);

  (1)linux系统中每个进程都有自己的一个文件描述符表,表中存储的是本进程打开的文件。

  (2)linux系统中有一个设计理念:一切届是文件。所以设备也是以文件的方式来访问的。我们要访问一个设备,就要去打开这个设备对应的文件描述符。譬如/dev/fb0这个设备文件就代表LCD显示器设备,/dev/buzzer代表蜂鸣器设备,/dev/console代表控制台设备。打开一个设备的文件就会得到这个设备的文件描述符(或者是文件描述符的编号),这个编号就代表这个设备,以后操作这个设备就用这个文件描述符来操作它

  (3)这里我们打开了/dev/console文件,并且复制了2次文件描述符,一共得到了3个文件描述符。这三个文件描述符分别是0、1、2.这三个文件描述符就是所谓的:标准输入、标准输出、标准错误。

  (4)进程1打开了三个标准输出输出错误文件,因此后续的进程1衍生出来的所有的进程默认都具有这3个三件描述符


4.2:挂载根文件系统,代码如下

1
2
3
4
if  (sys_access(( const  char  __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}

  (1)prepare_namespace函数中挂载根文件系统

  (2)根文件系统在哪里?根文件系统的文件系统类型是什么? uboot通过传参来告诉内核这些信息。uboot传参中的root=/dev/mmcblk0p2 rw 这一句就是告诉内核根文件系统在哪里uboot传参中的rootfstype=ext3这一句就是告诉内核rootfs的类型。

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

  (4)如果内核启动时挂载rootfs失败,则后面肯定没法执行了。内核中设置了启动失败休息5s自动重启的机制,因此这里会自动重启,所以有时候大家会看到反复重启的情况。

  (5)如果挂载rootfs失败,可能的原因有:最常见的错误就是uboot的bootargs设置不对。rootfs烧录失败(fastboot烧录不容易出错,以前是手工烧录很容易出错)rootfs本身制作失败的。(尤其是自己做的rootfs,或者别人给的第一次用)


5:执行用户态下的进程1程序

  (1)上面一旦挂载rootfs成功,则进入rootfs中寻找应用程序的init程序,

这个程序就是用户空间的进程1.找到后用run_init_process(里面的kernel_execve函数)去执行他

  (2)我们如果确定init程序是谁?方法是:先从uboot传参cmdline中看有没有指定,如果有指定先执行cmdline中指定的程序。cmdline中的init=/linuxrc这个就是指定rootfs中哪个程序是init程序。这里的指定方式就表示我们rootfs的根目录下面有个名字叫linuxrc的程序,这个程序就是init程序。如果uboot传参cmdline中没有init=xx或者cmdline中指定的这个xx执行失败,还有备用方案。

第一备用:/sbin/init,第二备用:/etc/init,第三备用:/bin/init,第四备用:/bin/sh。

如果以上都不成功,则kernel启动失败




本文转自 菜鸟养成记 51CTO博客,原文链接:http://blog.51cto.com/11674570/1840899
相关文章
|
2天前
|
Linux Android开发 开发者
linux m、mm、mmm函数和make的区别
通过理解和合理使用这些命令,可以更高效地进行项目构建和管理,特别是在复杂的 Android 开发环境中。
33 18
|
10天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
65 13
|
22天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
22天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
23天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
23天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
25天前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
34 3
|
4月前
|
Linux
Linux0.11 文件打开open函数(五)
Linux0.11 文件打开open函数(五)
57 0
|
7月前
|
Linux 开发者
Linux文件编程(open read write close函数)
通过这些函数,开发者可以在Linux环境下进行文件的读取、写入和管理。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
177 4
|
7月前
|
存储 Linux
Linux文件编程(open read write close函数)
Linux文件编程(open read write close函数)
253 0