目录
概述
1 启动内核第二阶段流程图
2 嵌入式Linux内核启动分析(C语言部分)
2.1 start_kernel()函数
2.2 rest_init()函数
2.3 kernel_init()函数
2.4 kernel_init_freeable()函数
概述
本文主要介绍linux内核启动(内核版本: linux4.1.15)第二部分的相关内容,第一部分主要是运行在汇编语言代码,其为C语言内核部分的运行创造环境。本文以start_kernel为起始函数,介绍其内部实现的功能,并对该函数内部调用的重要接口做了详细的介绍。
源代码下载地址:
【免费】NXP官方原版Uboot和Linux资源-CSDN文库
1 启动内核第二阶段流程图
第二阶段主要完成初始化内核启动的全部工作,包括外围硬件的启动,创建第一个工作进程:init进程、初始化控制台、加载rootfs文件系统等。该阶段的执行流程如下:
流程图一
流程图二
2 嵌入式Linux内核启动分析(C语言部分)
第二阶段是以执行main.c文件中的start_kernel()函数开始的,下面分析start_kernel()调用函数所实现的功能。
该文件在内核中的位置:init/main.c
2.1 start_kernel()函数
在执行 arch/arm/kernel/head-common.S文件中的__mmap_switched函数之后,内核跳到C语言部分运行。在软件项目中,一个项目的入口文件为main.c,对Linux内核也不例外,该文件位于:
/init/main.c
打开该文件,在代码:492行,找到这个函数。下面分段阅读函数实现的功能
代码 501 行: 初始化 lockdep 是死锁检测模块 ,作用是跟踪每个锁的自身状态和各个锁之间的依赖关系,经过规则验证来保证依赖的关系正确。
代码 502 行: 设置任务栈结束魔术数 , 用于栈溢出检测
代码 503 行: 启动SMP 有关(多核处理器),设置处理器 ID。
代码 504行: 初始化和debug相关的代码
代码 509 行: 栈溢出检测初始化
代码 511 行: cgroup 初始化, cgroup 用于控制 Linux 系统资源
代码 513 行: 关闭当前 CPU 中断
代码 514 行:boot中断disable标志量配置为true,此时boot相关的中断也不会开启
代码 520 行: 跟 CPU 有关的初始化
代码 521 行:页地址相关的初始化
代码 523 行:架构相关的初始化,此函数会解析传递进来的ATAGS 或者设备树(DTB)文件。会根据设备树里面的 model 和 compatible 这两个属性值来查找Linux 是否支持这个单板。此函数也会获取设备树中 chosen 节点下的 bootargs 属性值来得到命令行参数,也就是 uboot 中的 bootargs 环境变量的值,获取到的命令行参数会保存到command_line 中。
代码 524 行:内存有关的初始化
代码 525 行:存储命令行参数
代码 526 行:如果只是 SMP(多核 CPU)的话,此函数用于获取 CPU 核心数量, CPU 数量保存在变量
代码 527 行: 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据
代码 528 行:准备 SMP 系统下的数据,以填充CPU的寄存器
代码 530行:建立系统内存页区(zone)链表
代码 531行:处理用于热插拔 CPU 的页
代码 534行:解析命令行中的 console 参数
代码 549行:设置 log 使用的缓冲区
代码 550行:构建 PID 哈希表, Linux 中每个进程都有一个 ID, 这个 ID 叫做 PID。通过构建哈希表可以快速搜索进程 信息结构体。
代码 551行:预先初始化 vfs(虚拟文件系统)的目录项和索引节点缓存
代码 552行:定义内核异常列表
代码 553行:完成对系统保留中断向量的初始化
代码 554行:内存管理初始化
代码 561行:初始化调度器,主要是初始化一些结构体
代码 566行:关闭优先级抢占
代码 567行:检查中断是否关闭,如果没有的话就关闭中断
代码 570行:IDR 初始化, IDR 是 Linux 内核的整数管理机制,也就是将一个整数 ID 与一个指针关联起来。
代码 574行: 跟踪调试相关初始化
代码 576行: 上下文跟踪调试相关初始化
代码 577行:基数树相关数据结构初始化
代码 579行:初始中断相关初始化,主要是注册 irq_desc 结构体变量,因为 Linux 内核使用 irq_desc 来描述一个中断。
代码 580行:中断初始化
代码 581行:tick 初始化
代码 583行:初始化定时器
代码 584行:初始化高精度定时器
代码 585行:软中断初始化
代码 587行:初始化系统时间
代码 594行: 使能中断
代码 603行:控制台初始化
代码 608行: 如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息
代码 615行:锁自测
代码 628行: kmemleak 初始化, kmemleak 用于检查内存泄漏
代码 629行:启动CPU 内存页配置
代码 634行: 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能,BogoMIPS 设置越大,说明 CPU 性能越好。
代码 635行:PID 位图初始化
代码 636行:生成 anon_vma slab 缓存
代码 647行:为对象的赋予资格(凭证)
代码 648行:初始化一些结构体以使用 fork 函数
代码 649行:给各种资源管理结构分配缓存
代码 650行:初始化缓冲缓存
代码 651行:初始化密钥
代码 652行:安全相关初始化
代码 654行:为 VFS 创建缓存
代码 655行:初始化信号
代码 657行:页回写初始化
代码 660行:初始化 cpuset, cpuset 是将 CPU 和内存资源以逻辑性和层次性集成的一种机制,是 cgroup 使用的子系统之一
代码 661行:初始化 cgroup
代码 662行:进程状态初始化
代码 665行: 检查写缓冲一致性
代码 678行: 调用rest_init() 函数,系统正常启动起来了
2.2 rest_init()函数
代码 387行: 调用函数 rcu_scheduler_starting,启动 RCU 锁调度器
代码 394行: 调用函数 kernel_thread 创建 kernel_init 进程,也就是 init 内核进程。 init 进程的 PID 为 1。 init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程序, init 进程就会实现从内核态到用户态的转变。
代码 396行:调用函数 kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。 kthreadd 进程负责所有内核进程的调度和管理。
代码 409行: 调用函数 cpu_startup_entry 来进入 idle 进程, cpu_startup_entry 会调用 cpu_idle_loop, cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。
2.3 kernel_init()函数
代码 932行: kernel_init_freeable 函数用于完成 init 进程的一些其他初始化工作
代码 943行: 如果存在“/init”程序的话就通过函数 run_init_process 来运行此程序。
代码 946行: 如果 ramdisk_execute_command 为空的话就看 execute_command 是否为空,反正不管如何一定要在根文件系统中找到一个可运行的 init 程序。 execute_command 的值是通过uboot 传递,在 bootargs 中使用“init=xxxx”就可以了,比如“init=/linuxrc”表示根文件系统中的 linuxrc 就是要执行的用户空间 init 程序。
代码 963~966 行,如果 ramdisk_execute_command 和 execute_command 都为空,那么就依次查找“/sbin/init”、“/etc/init”、“/bin/init”和“/bin/sh”,这四个相当于备用 init 程序,如果这四个也不存在,那么 Linux 启动失败!
2.4 kernel_init_freeable()函数
代码 1002行: do_basic_setup 函数用于完成 Linux 下设备驱动初始化工作!非常重要。do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化
代码 1005行: 打开设备“/dev/console”,在 Linux 中一切皆为文件!因此“/dev/console”也是一个文件,此文件为控制台设备。每个文件都有一个文件描述符,此处打开的“/dev/console”文件描述符为 0,作为标准输入(0)。
代码 1008 和 1009 行: sys_dup 函数将标准输入(0)的文件描述符复制了 2 次,一个作为标准输出(1),一个作为标准错误(2)。这样标准输入、输出、错误都是/dev/console 了。 console 通过uboot 的 bootargs 环境变量设置,“console=ttymxc0,115200”表示将/dev/ttymxc0 设置为 console,也就是 I.MX6U 的串口 1。当然,也可以设置其他的设备为 console,比如虚拟控制台 tty1,设置 tty1 为 console 就可以在 LCD 屏幕上看到系统的提示信息。
代码 1020 行:调用函数 prepare_namespace 来挂载根文件系统。 根文件系统也是由命令行参数指定的,就是 uboot 的 bootargs 环境变量。比如“root=/dev/mmcblk1p2 rootwait rw”就表示根文件系统在/dev/mmcblk1p2 中,也就是 EMMC 的分区 2 中。