内核启动过程分析
【摘要】 目录一、内核源码获取查看1.1、Source Insight使用 二、查看链接脚本三、分析head.S3.1、到入口前代码3.2、内核启动的汇编阶段四、main.c内核启动的c语言阶段4.1、内核打印函数printk4.2、启动信息五、rest_init函数5.1、进程0、进程1、进程2编辑5.2、init进程的2种状态5.3、init进程干了什么六、宏MACHINE_START6.1、...
目录
一、内核源码获取查看
- 拿到Ubuntu中解压编译,
- 打包源码到window下
- 使用Source Insight阅读代码
1.1、Source Insight使用
- 创建工程
- 选择保存路径和工程名称
- 点击Add Tree
- 点击,完成最后一步
二、查看链接脚本
- kernel的连接脚本并不是直接提供的,而是提供了一个汇编文件vmlinux.lds.S,然后在编译的时候再去编译这个汇编文件得到真正的链接脚本vmlinux.lds
- vmlinux.lds.S在arch/arm/kernel/目录下
- 从vmlinux.lds中ENTRY(stext)可以知道入口符号是stext
- 查找发现2个文件中有ENTRY(stext)
head-nommu.S ,head.S - head.S是启用了MMU情况下的kernel启动文件
head-nommu.S是未使用mmu情况下的kernel启动文件
在加上查看编译后的文件发现,有head.o文件,所以可以确定是head.S
三、分析head.S
3.1、到入口前代码
- KERNEL_RAM_VADDR,这个宏定义了内核运行时的虚拟地址。
- 入口就是ENTRY(stext)处,前面的__HEAD定义了后面的代码属于段名为.head.text的段
- 阅读注释
内核的起始部分代码是被解压代码调用的,内核运行是有条件的
MMU = 关, D-cache = 关, I-cache = 不在乎, r0 = 0, r1 = 机器码, r2 = tag地址
此代码基本上是位置无关的,因此将内核链接到0xc0008000
有关机器的完整列表,请参见arch/arm/tools/mach-types
3.2、内核启动的汇编阶段
- __hyp_stub_install
arm虚拟化功能有关 - __lookup_processor_type
从cp15协处理器的c0寄存器中读取出硬件的CPU ID号
然后调用这个函数来进行合法性检验,如果合法则继续启动,如果不合法则停止启动转向__error_p启动失败 - __vet_atags
校验uboot给内核的传参ATAGS格式是否正确 - __create_page_tables
这个函数用来建立页表
kernel建立页表其实分为2步:kernel先建立了一个段式页表,再去建立一个细页表
启动的早期建立段式页表,并在内核启动前期使用;内核启动后期就会建立细页表并启用 - __mmap_switched
复制数据段、清除bss段(目的是构建C语言运行环境)
保存起来cpu id号、机器码、tag传参的首地址
b start_kernel跳转到C语言运行阶段
四、main.c内核启动的c语言阶段
start_kernel函数位于main.c中
位置在:linux-rpi-4.14.y/init/main.c
4.1、内核打印函数printk
- printk函数的用法和printf几乎一样,不同之处是可以用一个宏来定义消息输出的级别
- 打印级别,是为了解决打印信息过多,无效信息会淹没有效信息这个问题
- 给每一个printk添加一个打印级别,级别定义0-7
分别代表8种输出的重要性级别,0表示最重要,7表示最不重要
- linux的控制台有一个消息过滤显示机制,控制台只会显示级别比我的控制台定义的级别高的消息
4.2、启动信息
- 启动信息前面的[]数字是启动所用时间
- 第一条启动信息是smp_setup_processor_id();函数打印的
smp就是对称多处理器
它读取当前物理CPU,映射到逻辑CPU0,设置当前task id为0,打印启动信息 - 第二条是pr_notice("%s", linux_banner);打印的
pr_notice可以理解为是printk函数的封装形式 - setup_arch 位置:arch/arm/kernel/setup.c
setup_processor函数用来查找CPU信息 - parse_early_param 解析cmdline传参和其他传参
- trap_init:设置异常向量表
mm_init:内存管理模块初始化
console_init:控制台初始化
等一系列初始化。。。
总结:start_kernel函数中调用了很多的xx_init函数,全都是内核工作需要的模块的初始化函数,这些初始化之后内核就具有了一个基本的可以工作的条件了
五、rest_init函数
- rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd
- 调用schedule_preempt_disabled->schedule函数开启了内核的调度系统,从此linux系统开始转起来了
- 最终调用cpu_startup_entry->do_idle函数结束了整个内核的启动。也就是说linux内核最终结束了一个函数cpu_idle
- linux内核最终的状态是:有事干的时候去执行有意义的工作,没活干的时候就去死循环
调度系统
5.1、进程0、进程1、进程2
- ubuntu下ps -aux可以看到当前系统运行的所有进程
进程0不是一个用户进程,而属于内核进程,所以不显示0进程 - 进程0:进程0就是idle进程,叫空闲进程,也就是死循环
进程1:kernel_init函数就是进程1,这个进程被称为init进程
进程2:kthreadd函数就是进程2,这个进程是linux内核的守护进程。这个进程是用来保证linux内核自己本身能正常工作的
5.2、init进程的2种状态
- init进程2种状态,内核态向用户态的转变
- init进程在内核态下面时,通过一个函数do_execve来执行一个用户空间编译连接的应用程序就跳跃到用户态了。注意这个跳跃过程中进程号是没有改变的
- init进程刚开始运行的时候是内核态,它属于一个内核线程,然后他自己运行了一个用户态下面的程序后把自己强行转成了用户态
- init进程在内核态:挂载根文件系统并试图找到用户态下的那个init程序
init程序和内核不在一起,在根文件下 - init进程在用户态:所有的用户进程都直接或者间接派生自init进程
- init启动了login进程、命令行进程、shell进程
5.3、init进程干了什么
- 打开了/dev/console控制台文件
复制了2次文件描述符,一共得到了3个文件描述符
这三个文件描述符分别是012,这三个文件描述符就是:标准输入、标准输出、标准错误
后续的进程1衍生出来的所有的进程默认都具有这3个三件描述符 - 内核态下的进程1,挂载根文件系统
kernel_init->kernel_init_freeable->prepare_namespace函数中挂载根文件系统
root=/dev/mmcblk0p2 这一句就是告诉内核根文件系统在哪里
rootfstype=ext4 告诉内核rootfs文件系统的类型
挂载成功 [ 2.395288] VFS: Mounted root (ext4 filesystem) readonly on device 179:2. - 用户态下的进程1
kernel_init->run_init_process执行用户态下的init程序
六、宏MACHINE_START
- 定义了一个结构体类型为machine_desc的结构体变量
- 这个结构体变量会被定义到一个特定段.arch.info.init
- 会被链接器链接到这个.arch.info.init段中
6.1、结构体 machine_desc
- nr:机器码
- name:开发板名字
- init_machine:硬件驱动的加载和初始化函数执行
6.2、树莓派内核的MACHINE_START
- 经过查找,发现BCM_2835机器码为4828
用的文件是board_bcm2835.c
grep "MACHINE_START(*BCM2835*" * -nr - 使用函数bcm2835_init,硬件驱动的加载和初始化函数执行
- 通过宏可以看出,树莓派没有用到机器码
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)