kernel 启动流程
一、概述
之前学习了uboot的启动流程,现在接着学习uboot的启动流程,关于 kernel 的启动流程分析的大佬也是很多的,这里还是通过流程的图的方式进行记录,为了像我一样的新手,直观的了解 kernel 的启动流程。
在 kernel 启动之前已将完成了 uboot 的启动,看到此笔记的小伙伴应该都知道,还不了解的可以看我之前的笔记:UBOOT 启动流程
二、kernel 文件目录介绍
在了解 kernel 启动之前,先了解一下源码的目录,通过下表可以初步了解源码目录的作用,想要了解更细一点的,可以看着两位博友的笔记:
- kernel目录介绍:https://www.jianshu.com/p/c9053d396fcb
- kernel 目录 解析:https://www.cnblogs.com/yuanfang/p/1920895.html
名称 | 描述 |
---|---|
arch | 架构相关目录 |
block | 块设备相关目录 |
crypto | 加密相关目录 |
Documentation | 文档相关目录 |
drivers | 驱动相关目录 |
fs | 文件系统相关目录 |
include | 头文件相关目录 |
init | 初始化相关目录 |
ipc | 进程间通信相关目录 |
kernel | 内核相关目录 |
lib | 库相关目录 |
LICENSES | 许可相关目录 |
mm | 内存管理相关目录 |
net | 网络相关目录 |
samples | 例程相关目录 |
scripts | 脚本相关目录 |
security | 安全相关目录 |
sound | 音频处理相关目录 |
tools | 工具相关目录 |
usr | 与 initramfs 相关的目录,用于生成 initramfs |
virt | 提供虚拟机技术(KVM) |
.config | Linux 最终使用的配置文件 |
.gitignore | git 工具相关文件 |
.mailmap | 邮件列表 |
.version | 与版本有关 |
.vmlinux.cmd | cmd 文件,用于生成 vmlinux |
COPYING | 版权声明 |
CREDITS | Linux 贡献者 |
Kbuild | Makefile 会读取此文件 |
Kconfig | 图形化配置界面的配置文件 |
MAINTAINERS | 维护者名单 |
Makefile | Linux 顶层 Makefile |
Module.xx、modules.xx | 一系列文件,和模块有关 |
README | Linux 描述文件 |
System.map | 符号表 |
vmlinux | 编译出来的、未压缩的 ELF 格式 Linux 文件 |
vmlinux.o | 编译出来的 vmlinux.o 文件 |
三、镜像文件
编译完成后,会生成 vmlinux、Image,zImage、uImage 文件,这里通过对不它们的区别,便可了解每个文件的作用
vmlinux
vmlinux 是编译出来的最原始的内核文件,没有经过压缩,所以文件比较大。Image
Image 是 Linux 内核镜像文件,仅包含可执行的二进制数据。是使用 objcopy 取消掉 vmlinux 中的一些其他信息,比如符号表等,虽然也没有压缩,但是文件比vmlinux小了很多。zImage
zImage 是经过 gzip 压缩后的 Image,一般烧写的都是 zImage 镜像文件uImage
uImage 是老版本 uboot 专用的镜像文件,uImag 是在 zImage 前面加了一个长度为 64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。
四、kernel 汇编启动阶段
vmlinux.lds
vmlinux.lds 是链接脚本,通过分析 kernel 顶层 Makefile 文件可知,镜像文件的打包是从 vmlinux.lds 链接脚本开始的,vmlinux.lds 文件位置在 arch/arm/kernel 目录下,在文件中使用了ENTRY(stext) 指定了入口 为 stext。stext
stext 在文件 arch/arm/kernel/head.S 中,主要完成了 kernel 的汇编启动阶段。safe_svcmode_maskall
safe_svcmode_maskall 在文件arch/arm/include/asm/assembler.h 中,主要作用,确保cpu处于SVC模式,并且关闭所有终端__lookup_processor_type
__lookup_processor_type 在文件 arch/arm/kernel/head-common.S 文件中,主要作用是检查当前系统是否支持此 CPU,如果支持的就获取procinfo信息。procinfo 是proc_info_list 类型的结构体 , proc_info_list 在文件arch/arm/include/asm/procinfo.h 中
__lookup_machine_type
__lookup_machine_type检测是否支持当前单板。__vet_atags
__vet_atags 在文件 arch/arm/kernel/head-common.S 中,主要作用是验证 atags 或设备树(dtb)的合法性。__fixup_smp
__fixup_smp 在当前文件中,主要作用是处理多核,通过宏CONFIG_SMP_ON_UP 开启。__create_page_tables
__create_page_tables 在文件文件 arch/arm/kernel/head.S中,主要作用是创建页表。__mmap_switched
__mmap_switched 在文件 arch/arm/kernel/head-common.S 中,主要作用是将函数__mmap_switched的地址保存到 r13 寄存器中,最终会调用start_kernel 函数__enable_mmu
__enable_mmu 在文件 arch/arm/kernel/head.S 中,主要作用是通过调用 _turn_mmu_on 来打开 MMU, _turn_mmu_on 最后会执行 r13 里面保存的_mmap_switched 函数。
五、kernel 初始化阶段
start_kernel 函数通过调用众多的子函数来完成 Linux 启动之前的一些初始化工作,由于start_kernel 函数里面调用的子函数太多,而这些子函数又很复杂,因此我们简单的来看一下一些重要的子函数。start_kernel 函数如下所示:
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
/* 设置任务栈结束魔术数,用于栈溢出检测 */
set_task_stack_end_magic(&init_task);
/* 跟 SMP 有关(多核处理器),设置处理器 ID。
* 有很多资料说 ARM 架构下此函数为空函数,
* 是因为那时候 ARM 还没有多核处理器。
*/
smp_setup_processor_id();
/* 做一些和 debug 有关的初始化 */
debug_objects_early_init();
/* Set up the the initial canary ASAP: */
boot_init_stack_canary();
/* cgroup 初始化,cgroup 用于控制 Linux 系统资源*/
cgroup_init_early();
/* 关闭当前 CPU 中断 */
local_irq_disable();
early_boot_irqs_disabled = true;
/****** 中断关闭期间做一些重要的操作,然后打开中断 ******/
/* 跟 CPU 有关的初始化 */
boot_cpu_init();
/* 页地址相关的初始化 */
page_address_init();
/* 打印 Linux 版本号、编译时间等信息 */
pr_notice("%s", linux_banner);
/* 架构相关的初始化,此函数会解析传递进来的
* ATAGS 或者设备树(DTB)文件。会根据设备树里面
* 的 model 和 compatible 这两个属性值来查找
* Linux 是否支持这个单板。此函数也会获取设备树
* 中 chosen 节点下的 bootargs 属性值来得到命令
* 行参数,也就是 uboot 中的 bootargs 环境变量的
* 值,获取到的命令行参数会保存到 command_line 中。
*/
setup_arch(&command_line);
#ifdef CONFIG_SS_PROFILING_TIME
// recode_timestamp_ext(0, "start_kernel+", t1);
recode_timestamp_init();
recode_timestamp(__LINE__, "setup_arch-");
#endif
/* 应该是和内存有关的初始化 */
mm_init_cpumask(&init_mm);
/* 好像是存储命令行参数 */
setup_command_line(command_line);
/* 如果只是 SMP(多核 CPU)的话,此函数用于获取
* CPU 核心数量,CPU 数量保存在变量 nr_cpu_ids 中。
*/
setup_nr_cpu_ids();
/* 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据 */
setup_per_cpu_areas();
boot_cpu_state_init();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
/* 建立系统内存页区(zone)链表 */
build_all_zonelists(NULL, NULL);
/* 处理用于热插拔 CPU 的页 */
page_alloc_init();
/* 打印命令行信息 */
pr_notice("Kernel command line: %s\n", boot_command_line);
/* 解析命令行中的 console 参数 */
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
jump_label_init();
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
/* 设置 log 使用的缓冲区*/
setup_log_buf(0);
/* 构建 PID 哈希表,Linux 中每个进程都有一个 ID,
* 这个 ID 叫做 PID。通过构建哈希表可以快速搜索进程
* 信息结构体。
*/
pidhash_init();
/* 预先初始化 vfs(虚拟文件系统)的目录项和索引节点缓存*/
vfs_caches_init_early();
/* 定义内核异常列表 */
sort_main_extable();
/* 完成对系统保留中断向量的初始化 */
trap_init();
/* 内存管理初始化 */
mm_init();
/* 初始化调度器,主要是初始化一些结构体 */
sched_init();
/* 关闭优先级抢占 */
preempt_disable();
/* 检查中断是否关闭,如果没有的话就关闭中断 */
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
/*允许及早创建工作队列和工作项排队/取消。工作项的
* 执行取决于 kthread,并在 workqueue_init()之后开始。
*/
idr_init_cache();
/* 初始化 RCU,RCU 全称为 Read Copy Update(读-拷贝修改) */
rcu_init();
/* 跟踪调试相关初始化 */
trace_init();
context_tracking_init();
/* 基数树相关数据结构初始化 */
radix_tree_init();
/* 初始中断相关初始化,主要是注册 irq_desc 结构体变
* 量,因为 Linux 内核使用 irq_desc 来描述一个中断。
*/
early_irq_init();
/* 中断初始化 */
init_IRQ();
/* tick 初始化 */
tick_init();
rcu_init_nohz();
/* 初始化定时器 */
init_timers();
/* 初始化高精度定时器 */
hrtimers_init();
/* 软中断初始化 */
softirq_init();
timekeeping_init();
/* 初始化系统时间 */
time_init();
sched_clock_postinit();
printk_nmi_init();
perf_event_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
/* 使能中断 */
local_irq_enable();
/* slab 初始化,slab 是 Linux 内存分配器 */
kmem_cache_init_late();
/* 初始化控制台,之前 printk 打印的信息都存放
* 缓冲区中,并没有打印出来。只有调用此函数
* 初始化控制台以后才能在控制台上打印信息。
*/
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
/* 如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息。*/
lockdep_info();
/* 锁自测 */
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_ext_init();
debug_objects_mem_init();
/* kmemleak 初始化,kmemleak 用于检查内存泄漏 */
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
/* 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能
* BogoMIPS 设置越大,说明 CPU 性能越好。
*/
calibrate_delay();
/* PID 位图初始化 */
pidmap_init();
/* 生成 anon_vma slab 缓存 */
anon_vma_init();
acpi_early_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
/* Should be run before the first non-init thread is created */
init_espfix_bsp();
#endif
thread_stack_cache_init();
/* 为对象的每个用于赋予资格(凭证) */
cred_init();
/* 初始化一些结构体以使用 fork 函数 */
fork_init();
/* 给各种资源管理结构分配缓存 */
proc_caches_init();
/* 初始化缓冲缓存 */
buffer_init();
/* 初始化密钥 */
key_init();
/* 安全相关初始化 */
security_init();
dbg_late_init();
/* 为 VFS 创建缓存 */
vfs_caches_init();
/* 初始化信号 */
signals_init();
/* 注册并挂载 proc 文件系统 */
page_writeback_init();
proc_root_init();
nsfs_init();
/* 初始化 cpuset,cpuset 是将 CPU 和内存资源以逻辑性
* 和层次性集成的一种机制,是 cgroup 使用的子系统之一
*/
cpuset_init();
/* 初始化 cgroup */
cgroup_init();
/* 进程状态初始化 */
taskstats_init_early();
delayacct_init();
/* 检查写缓冲一致性 */
check_bugs();
acpi_subsystem_init();
sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
ftrace_init();
/* rest_init 函数 */
rest_init();
}
rcu_scheduler_starting
rcu_scheduler_starting 主要作用是启动 RCU 锁调度器kernel_thread
函数 kernel_thread 创建 kernel_init 进程,也就是 init 内核进程。init 进程的 PID 为 1。init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程序,init 进程就会实现从内核态到用户态的转变kernel_thread
kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthreadd
进程负责所有内核进程的调度和管理。cpu_startup_entry
cpu_startup_entry 进入 idle 进程,cpu_startup_entry 会调用 cpu_idle_loop,cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。idle 进程的 PID 为 0,idle 进程叫做空闲进程。kernel_init_freeable
kernel_init_freeable 函数用于完成 init 进程的一些其他初始化工作。do_basic_setup
do_basic_setup 函数用于完成 Linux 下设备驱动初始化工作!非常重要。do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化。prepare_namespace
prepare_namespace 是挂载根文件系统。根文件系统也是由命令行参数指定的,也就是 uboot 的 bootargs 环境变量。比如“root=/dev/mmcblk1p3 rootwait rw”就表示根文件系统在/dev/mmcblk1p3 中,也就是 EMMC 的分区 3 中。
参考链接
kernel目录介绍:https://www.jianshu.com/p/c9053d396fcb
kernel 目录 解析:https://www.cnblogs.com/yuanfang/p/1920895.html
kernel 启动流程的更多相关文章
- linux kernel启动流程
linux kernel启动是从./init/main.c中开始的,其大概流程是: 1. 调用start_kernel()函数: 2. start_kernel()调用rest_init()函数: 3 ...
- andriod and linux kernel启动流程
虽然这里的Arm Linux kernel前面加上了Android,但实际上还是和普遍Arm linux kernel启动的过程一样的,这里只是结合一下Android的Makefile,讲一下boot ...
- Linux Kernel系列一:开篇和Kernel启动概要
前言 近期几个月将Linux Kernel的大概研究了一下,以下须要进行深入具体的分析.主要将以S3C2440的一块开发板为硬件实体.大概包含例如以下内容: 1 bootloader分析,以uboot ...
- [uboot] (第五章)uboot流程——uboot启动流程
http://blog.csdn.net/ooonebook/article/details/53070065 以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为 ...
- ARM Linux从Bootloader、kernel到filesystem启动流程
转自:http://www.veryarm.com/1491.html ARM Linux启动流程大致为:bootloader ---->kernel---->root filesyste ...
- linux启动流程及自定义gurb
linux 启动流程 POST BIOS(boot sequence) 所选择的启动设备次序的MBR中是否有引导程序, ----> MBR(bootloader) 提供内核列表 -------& ...
- 嵌入式Linux驱动学习之路(五)u-boot启动流程分析
这里说的u-boot启动流程,值得是从上电开机执行u-boot,到u-boot,到u-boot加载操作系统的过程.这一过程可以分为两个过程,各个阶段的功能如下. 第一阶段的功能: 硬件设备初始化. 加 ...
- Kernel启动时 驱动是如何加载的module_init,加载的次序如何;略见本文
Init.h中有相关initcall的启动次序,在system.map中可看出具体的__initcall指针的前后次序 #define pure_initcall(fn) __define_initc ...
- CentOS6 启动流程图文解剖
我们在使用Linux操作系统的时候,我们只需按下电源键,等待,然后输入账户和密码就可以使用Linux操作系统了.那么在按下电源到输入账号和密码之前,操作系统都做了些什么?下面就来讲述在这段时间发生的动 ...
- centos启动流程[转]
启动流程概览 在硬件驱动成功后,Kernel 会主动呼叫 init 程序,而 init 会取得 run-level 资讯: init 运行 /etc/rc.d/rc.sysinit 文件来准备软件运行 ...
随机推荐
- CentOS6 配置阿里云 NTP 服务
本文以Centos 6.5为例介绍如何修改Linux实例时区,以及开启和配置Linux NTP服务,保证实例本地时间精确同步. 前提条件 NTP服务的通信端口为UDP 123,设置NTP服务之前请确保 ...
- .NET下数据库的负载均衡(有趣实验)
相关下载: 数据库的负载均衡-示例代码(dp1-DbBalance.rar) 数据库的负载均衡-示例代码(dp1-DbBalance.rar) 支持.Net/.Net Core/.Net Framew ...
- NSIS查找文本中是否包含某个字串
!include "textfunc.nsh"!include "logiclib.nsh"OutFile "find.exe"#文本文件a ...
- 云原生下基于K8S声明式GitOps持续部署工具ArgoCD实战-上
@ 目录 概述 定义 工作原理 主要组件 核心概念 环境准备 概述 安装Kubekey 创建K8S 安装K9S OpenLB 安装ArgoCD 安装 ArgoCD CLI 从Git库中创建一个应用程序 ...
- 使用idea操作git(ssh协议)
问题 我们发现,使用IDEA上的git功能,当使用ssh协议出现了可以commit但无法push和pull的问题,经过测试发现原因是Could not read from remsitory.直接翻译 ...
- Redis5种数据类型
字符串 @GetMapping("/string") public String stringTest(){ redisTemplate.opsForValue().set(&qu ...
- Python 嵌入式打包 (图文)
Python嵌入式打包过程 目录 Python嵌入式打包过程 下载嵌入式包 解压和配置 安装pip和其他依赖 启动项目 python嵌入式打包:将python环境与项目代码打包到同一个文件夹中,在其他 ...
- 十五、资源控制之Deployment
资源控制器之Deployment Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义(declarative)方法,用来替代以前的ReplicationControlle ...
- .NET性能系列文章二:Newtonsoft.Json vs. System.Text.Json
微软终于追上了? 图片来自 Glenn Carstens-Peters Unsplash 欢迎来到.NET性能系列的另一章.这个系列的特点是对.NET世界中许多不同的主题进行研究.基准和比较.正如标题 ...
- SpringBoot 过滤器和拦截器
过滤器 实现过滤器需要实现 javax.servlet.Filter 接口.重写三个方法.其中 init() 方法在服务启动时执行,destroy() 在服务停止之前执行. 可用两种方式注册过滤器: ...