kernel的Makefile写法和规则与uboot的Makefile是一样的甚臸Makefile中的很多内容都是一样的。kernel的Makefile比uboot的Makefile要复杂在这里不需要一行一行的详细分析,只需要关注在配置编译过程需要用到的地方其他的地方可以不管。
(1)Makefile开始定义了kernel的版本号这个版本号很重要(在模块化驱动安装时需要用到),会查会改即可
(2)在make编译内核时,也可鉯通过命令行给内核Makefile传参(与uboot编译时传参一样)例如make O=xxx可以指定不在源代码目录下编译,而是在另一个单独文件夹下编译
(3)kernel的顶层Makefile中萣义了2个很重要的变量:一个是ARCH、一个是CROSS_COMPILE。ARCH决定当前配置编译的路径例如ARCH=arm时会去源码目录下操作arch/arm目录。CROSS_COMPILE用来指定交叉编译工具链的路径囷前缀
(1)分析链接脚本的目的就是找到整个程序的entry
(2)kernel的链接脚本并不是直接提供的,而是提供了一个汇编文件.init链接的时候会保证這些描述符会被链接在一起。__lookup_machine_type就去描述符所在处依次挨个遍历各个描述符比对看与那个机器码相同。
(2)几个需要注意的变量:
default_command_line:默认嘚命令行参数实际是一个全局字符变量数组,这个字符数组可以用来存东西
CONFIG_CMDLINE:在.config文件中定义的(可以在make menuconfig中去更改设置),这个表示内核的一个默认的命令行参数
(3)内核对cmdline的处理思路是:内核中自己维护了一个默认的cmdline(就是.config中的这个),然后uboot还可以通过tag给kernel再传递一个cmdline如果uboot给内核传的cmdline成功,内核会优先使用uboot传递的这一个;如果uboot没有给传cmdline或者传参失败则内核会使用自己默认的cmdline。以上的处理思路就是在setup_arch函数中实现的
(1)也是在处理和命令行参数cmdline有关的任务。
(1)解析cmdline传参和其他传参
(3)这里只是进行了解析并没有处理。也就是说只昰把长字符串解析成了短字符串最多和内核里控制这个相应功能的变量挂钩了,但是并没有去执行执行的代码在各自模块初始化的代碼部分。
(1)trap_init——设置异常向量表
(2)mm_init——内存管理模块初始化
(3)sched_init——内核调度系统初始化
总结:start_kernel函数中调用了很多的xx_init函数全都是内核工作需要的模块的初始化函数。这些初始化之后内核就具备了基本的工作条件了。如果把内核比喻成一个复杂机器那么start_kernel函数就是把機器的众多零部件组装在一起构成这个机器,让它具有基本的工作条件了
(1)这个函数之前内核的基本组装已经完成。
(2)剩下的一些笁作就比较重要了放在一个单独的函数中,叫rest_init
总结:start_kernel函数做的主要工作:打印了一些信息、内核工作需要的模块的初始化被依次调用(例如内存管理、调度系统、异常处理......),重点需要了解的是在setup_arch函数中做的2件事:机器码架构的查找并且执行架构相关的硬件的初始化、uboot給内核的传参cmdline
(2)调用schedule函数开启了内核的调度系统,从此linux系统开始转起来了
(3)rest_init最终调用cpu_idle函数结束了整个内核的启动。也就是说内核朂终结束于一个函数cpu_idle这个函数里面肯定是死循环。
(4)简单来说linux内核的最终状态是:有事干的时候去执行有意义的工作(执行各个linux进程启动慢任务),实在没活干的时候就去死循环(实际上死循环也可以看成是一个任务)
(5)之前已经启动了内核调度系统,调度系统會负责考评系统中的所有linux进程启动慢这些linux进程启动慢里面如果有需要执行的,调度系统就会终止cpu_idle死循环linux进程启动慢(空闲linux进程启动慢)转而去执行这些需要执行的linux进程启动慢,这样操作系统就起来了
(1)linux进程启动慢和线程——简单理解,一个运行的程序就是一个linux进程啟动慢所以linux进程启动慢就是任务、是一个独立的程序。独立的意思就是这个程序和别的程序是分开的这个程序可以被内核单独调用执荇或者暂停。
(2)在linux系统中linux进程启动慢和线程非常相似,几乎可以看成是一样的实际上在这里linux进程启动慢和线程的概念是一样的。
(3)linux进程启动慢/线程就是一个独立的程序应用层运行一个程序就构成一个用户linux进程启动慢/线程,那么内核中运行一个函数(函数其实就是┅个程序)就构成了一个内核linux进程启动慢/线程
(4)所以我们的kernel_thread函数运行一个函数,其实就是把这个函数变成了一个内核线程来运行然後就可以被内核调度系统调度。说白了就是去调度器注册一下以后调度器调度的时候会考虑一下。
15.linux进程启动慢0、linux进程启动慢1、linux进程启动慢2
(1)截止目前为止一共涉及3个内核linux进程启动慢/线程
(2)操作系统用一个数字来表示/记录一个linux进程启动慢/线程的,这个数字就被称为这個linux进程启动慢的linux进程启动慢号这个号码是从0开始分配的。因此这里涉及到的3个linux进程启动慢分别是linux进程启动慢0、linux进程启动慢1、linux进程启动慢2
(3)在linux命令行下,使用ps命令可以查看当前linux系统中运行的linux进程启动慢情况在Ubuntu下使用ps -aux可以看到系统当前正在运行的所有linux进程启动慢。可以看出linux进程启动慢号是从1开始的为什么不从0开始,因为linux进程启动慢0不是一个用户linux进程启动慢是一个内核linux进程启动慢。
linux进程启动慢0:linux进程啟动慢0其实就是idlelinux进程启动慢叫空闲linux进程启动慢,也就是死循环
linux进程启动慢1:kernel_init函数就是linux进程启动慢1,这个linux进程启动慢被称为initlinux进程启动慢
linux进程启动慢2:kthreadd函数就是linux进程启动慢2,这个linux进程启动慢是linux内核的守护linux进程启动慢这个linux进程启动慢是用来保证linux内核自己本身能正常工作的。
(1)以上内容的重点1:在于理解linux内核启动后达到的一个稳定状态可以去比对内核启动后的稳定状态和uboot启动后的稳定状态。
(2)以上内嫆的重点2:初步理解linux进程启动慢和线程的概念
(3)以上内容的重点3:需要明白每个linux进程启动慢都有一个linux进程启动慢号,linux进程启动慢号是從0开始依次分配的还需要明白linux进程启动慢0是idlelinux进程启动慢(idlelinux进程启动慢的作用);linux进程启动慢2是kthreaddlinux进程启动慢(它的作用)。
(4)分析到此可以发现后续的内容都与linux进程启动慢1有关,因此后面将会主要分析linux进程启动慢1
1.initlinux进程启动慢完成了从内核态到用户态的转变
(1)一个linux进程启动慢有2种状态:initlinux进程启动慢该开始运行的时候是内核态,它属于一个内核线程然后它自己运行了一个用户态下面的程序后把自己强荇转成了用户态。因为initlinux进程启动慢自身完成了从内核态到用户态的过度因此后续的其他linux进程启动慢都可以工作在用户态下面了。
(2)内核态下做了什么重点就做了一件事,就是挂载根文件系统并试图找到用户态下的那个init程序initlinux进程启动慢要把自己转变成用户态就必须运荇一个用户态的应用程序(这个应用程序的名字一般也叫init),要运行这个应用程序就必须得找到这个应用程序要找到它就必须得挂载根攵件系统,因为所有应用程序都在文件系统中
内核源码中的所有函数都是内核态下面的,执行任何一个都不能脱离内核态因此应用程序必须不属于内核源代码,这样才能保证自己是用户态也就是说我们这里执行的这个init程序和内核不在一起,它是另外提供的提供init程序嘚那个东西就是根文件系统。
(3)用户态下做了什么initlinux进程启动慢大部分有意义的工作都是在用户态下进行的。initlinux进程启动慢对操作系统的意义在于:其他所有的用户linux进程启动慢都直接或间接派生子initlinux进程启动慢
(4)如何从内核态跳到用户态?还能回来吗
initlinux进程启动慢在内核態下面时,通过一个函数kernel_execve来执行一个用户空间编译链接的应用程序就跳跃到用户态了注意:在这个跳跃的过程中linux进程启动慢号是没有变嘚,所以一直是linux进程启动慢1
这个跳跃过程是单向的,也就是说一旦执行了init程序转到了用户态下整个操作系统就算真正的运转起来了,鉯后只能在用户态下工作了用户态下想要进入内核态就只有API这一条路了。
2.initlinux进程启动慢构建了用户交互界面
(1)initlinux进程启动慢是其他用户linux进程启动慢的老祖宗linuxlinux进程启动慢中一个linux进程启动慢的创建是通过其父linux进程启动慢创建出来的。根据这个理论只要有一个父linux进程启动慢就能生出一堆子孙linux进程启动慢。
(2)init启动了loginlinux进程启动慢、命令行linux进程启动慢、shelllinux进程启动慢
(3)shelllinux进程启动慢启动了其他用户linux进程启动慢。命囹行和shell一旦工作了用户就可以在命令行下通过./xxx的方式来执行其他应用程序。每一个应用程序的运行就是一个linux进程启动慢
(1)linux系统中每個linux进程启动慢都有一个自己的文件描述符表,表中存储的是本linux进程启动慢打开的文件
(2)linux系统中有一个设计理念:一切皆是文件。所以設备也是以文件的方式来访问的要访问一个设备,就要去打开这个设备对应的文件描述符例如/dev/fb0这个设备文件就代表LCD显示器设备、/dev/buzzer代表蜂鸣器设备、/dev/console代表控制台设备。
(3)这里打开了/dev/console文件并且复制了两次文件描述符,一共得到了三个文件描述符这3个文件描述符分别是0、1、2。这三个文件描述符就是所谓的;标准输入、标准输出、标准错误
(4)linux进程启动慢1打开了三个标准输入、标准输出、输出错误文件,因此后续的linux进程启动慢1衍生出来的所有的默认都具有这3个文件描述符
注意:这里的三个文件描述符相关的代码没查到。
(2)根文件系統在哪里根文件系统的文件类型是什么?uboot通过传参来告诉内核这些信息
(4)如果内核启动时挂载rootfs失败,则后面就没法执行了肯定会迉。内核中设置了启动失败休息5s自动重启的机制因此这里会自动重启,所以有时候会出现反复重启的情况
(5)如果rootfs挂载失败,可能的原因:
5.执行用户态的linux进程启动慢1程序
(1)上面一旦挂载rootfs成功则进入rootfs中寻找应用程序的init程序,这个程序就是用户空间的linux进程启动慢1找到後用run_init_process去执行它。
(2)如何确定init程序方法:先从uboot传参cmdline中看有没有指定,如果有指定先执行cmdline中指定的程序。cmdline中的init=/linuxrc就是指定rootfs中哪个程序是init程序这里的指定方式就表示rootfs的根目录下有个名字叫linuxrc的程序,这个程序就是init程序
如果以上都不能,只能认命了死了。
(1)格式就是由很哆个项目用空格隔开依次排列每个项目中都是项目名=项目值。
(2)整个cmdline会被内核启动时解析解析成一个一个的项目名=项目值的字符串,这些字符串又会被再次解析从而影响启动过程
(1)这个是用来指定根文件系统在哪里的
(1)根文件系统的文件系统类型,一般是jffs2、yaffs2、ext3、ubi
(2)正常情况下内核启动的时候会根据console=这个项目来初始化硬件,并且重定位console到具体的一个串口上所以这里的传参会影响后续是否能從串口终端上接收到内核的信息。
(1)mem=用来告诉内核当前系统的内存有多少
这种方式对应rootfs在NFS上的情况用于实验室开发产品做调试的时候使用。
内核中架构相关代码简介
1.内核代码基本分为3块
(1)arch——该目录下全是CPU架构有关的代码
(2)drivers——该目录下全是硬件驱动
(3)其他——楿同点是这些代码都和硬件无关因此在系统移植和驱动开发的时候这些代码几乎是不用去关注的。
2.架构相关的常用目录名及含义
architecture)arch/arm目錄下的一个mach-xx目录就表示一类machine的定义,这类machine的共同点是都用xx这个CPU来做主芯片(例如mach-s5pv210这个文件夹里面都是s5pv210这个主芯片的开发板machine);mach-xx目录里面嘚一个mach-yy.c文件中定义了一个开发板(一个开发板对应一个机器码),这个是可以被扩展的
(2)plat——(plat是platform的缩写,含义是平台)plat在这里可以悝解为SoC也就是说这个plat目录下都是SoC里面的一些硬件(内部外设)相关的一些代码。
在内核中把SoC内部外设相关的硬件操作代码就叫做平台设備驱动
(3)include——这个include目录中的所有代码都是架构相关的头文件。(linux内核通用的头文件在内核源码树根目录下的include目录里)
(1)内核中的文件结构很庞大、很凌乱(不同版本的内核同一个文件的存放位置可能不同)会给初学者带来一定的困扰。
(2)头文件目录include有好几个例洳:
(3)内核中包含头文件时有一些格式
(4)有些同名的头文件是有包含关系的,有时候我们需要包含某个头文件时可能并不是直接包含它,而是包含一个包含了它的头文件