一、概述

之前学习了uboot的启动流程,现在接着学习uboot的启动流程,关于 kernel 的启动流程分析的大佬也是很多的,这里还是通过流程的图的方式进行记录,为了像我一样的新手,直观的了解 kernel 的启动流程。

在 kernel 启动之前已将完成了 uboot 的启动,看到此笔记的小伙伴应该都知道,还不了解的可以看我之前的笔记:UBOOT 启动流程

二、kernel 文件目录介绍

在了解 kernel 启动之前,先了解一下源码的目录,通过下表可以初步了解源码目录的作用,想要了解更细一点的,可以看着两位博友的笔记:

名称 描述
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 文件,这里通过对不它们的区别,便可了解每个文件的作用

  1. vmlinux

    vmlinux 是编译出来的最原始的内核文件,没有经过压缩,所以文件比较大。

  2. Image

    Image 是 Linux 内核镜像文件,仅包含可执行的二进制数据。是使用 objcopy 取消掉 vmlinux 中的一些其他信息,比如符号表等,虽然也没有压缩,但是文件比vmlinux小了很多。

  3. zImage

    zImage 是经过 gzip 压缩后的 Image,一般烧写的都是 zImage 镜像文件

  4. uImage

    uImage 是老版本 uboot 专用的镜像文件,uImag 是在 zImage 前面加了一个长度为 64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。

四、kernel 汇编启动阶段

  1. vmlinux.lds

    vmlinux.lds 是链接脚本,通过分析 kernel 顶层 Makefile 文件可知,镜像文件的打包是从 vmlinux.lds 链接脚本开始的,vmlinux.lds 文件位置在 arch/arm/kernel 目录下,在文件中使用了ENTRY(stext) 指定了入口 为 stext。

  2. stext

    stext 在文件 arch/arm/kernel/head.S 中,主要完成了 kernel 的汇编启动阶段。

  3. safe_svcmode_maskall

    safe_svcmode_maskall 在文件arch/arm/include/asm/assembler.h 中,主要作用,确保cpu处于SVC模式,并且关闭所有终端

  4. __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 中

  5. __lookup_machine_type

    __lookup_machine_type检测是否支持当前单板。

  6. __vet_atags

    __vet_atags 在文件 arch/arm/kernel/head-common.S 中,主要作用是验证 atags 或设备树(dtb)的合法性。

  7. __fixup_smp

    __fixup_smp 在当前文件中,主要作用是处理多核,通过宏CONFIG_SMP_ON_UP 开启。

  8. __create_page_tables

    __create_page_tables 在文件文件 arch/arm/kernel/head.S中,主要作用是创建页表。

  9. __mmap_switched

    __mmap_switched 在文件 arch/arm/kernel/head-common.S 中,主要作用是将函数__mmap_switched的地址保存到 r13 寄存器中,最终会调用start_kernel 函数

  10. __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();
}
  1. rcu_scheduler_starting

    rcu_scheduler_starting 主要作用是启动 RCU 锁调度器

  2. kernel_thread

    函数 kernel_thread 创建 kernel_init 进程,也就是 init 内核进程。init 进程的 PID 为 1。init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程序,init 进程就会实现从内核态到用户态的转变

  3. kernel_thread

    kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthreadd

    进程负责所有内核进程的调度和管理。

  4. cpu_startup_entry

    cpu_startup_entry 进入 idle 进程,cpu_startup_entry 会调用 cpu_idle_loop,cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。idle 进程的 PID 为 0,idle 进程叫做空闲进程。

  5. kernel_init_freeable

    kernel_init_freeable 函数用于完成 init 进程的一些其他初始化工作。

  6. do_basic_setup

    do_basic_setup 函数用于完成 Linux 下设备驱动初始化工作!非常重要。do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化。

  7. 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 启动流程的更多相关文章

  1. linux kernel启动流程

    linux kernel启动是从./init/main.c中开始的,其大概流程是: 1. 调用start_kernel()函数: 2. start_kernel()调用rest_init()函数: 3 ...

  2. andriod and linux kernel启动流程

    虽然这里的Arm Linux kernel前面加上了Android,但实际上还是和普遍Arm linux kernel启动的过程一样的,这里只是结合一下Android的Makefile,讲一下boot ...

  3. Linux Kernel系列一:开篇和Kernel启动概要

    前言 近期几个月将Linux Kernel的大概研究了一下,以下须要进行深入具体的分析.主要将以S3C2440的一块开发板为硬件实体.大概包含例如以下内容: 1 bootloader分析,以uboot ...

  4. [uboot] (第五章)uboot流程——uboot启动流程

    http://blog.csdn.net/ooonebook/article/details/53070065 以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为 ...

  5. ARM Linux从Bootloader、kernel到filesystem启动流程

    转自:http://www.veryarm.com/1491.html ARM Linux启动流程大致为:bootloader ---->kernel---->root filesyste ...

  6. linux启动流程及自定义gurb

    linux 启动流程 POST BIOS(boot sequence) 所选择的启动设备次序的MBR中是否有引导程序, ----> MBR(bootloader) 提供内核列表 -------& ...

  7. 嵌入式Linux驱动学习之路(五)u-boot启动流程分析

    这里说的u-boot启动流程,值得是从上电开机执行u-boot,到u-boot,到u-boot加载操作系统的过程.这一过程可以分为两个过程,各个阶段的功能如下. 第一阶段的功能: 硬件设备初始化. 加 ...

  8. Kernel启动时 驱动是如何加载的module_init,加载的次序如何;略见本文

    Init.h中有相关initcall的启动次序,在system.map中可看出具体的__initcall指针的前后次序 #define pure_initcall(fn) __define_initc ...

  9. CentOS6 启动流程图文解剖

    我们在使用Linux操作系统的时候,我们只需按下电源键,等待,然后输入账户和密码就可以使用Linux操作系统了.那么在按下电源到输入账号和密码之前,操作系统都做了些什么?下面就来讲述在这段时间发生的动 ...

  10. centos启动流程[转]

    启动流程概览 在硬件驱动成功后,Kernel 会主动呼叫 init 程序,而 init 会取得 run-level 资讯: init 运行 /etc/rc.d/rc.sysinit 文件来准备软件运行 ...

随机推荐

  1. ERP是什么呢?

    ERP(Enterprise Resource Planning,企业资源计划)系统,是进行物质资源.资金资源和信息资源集成一体化管理的企业信息管理系统,ERP统领企业全局,为管理层服务,重心在于企业 ...

  2. 微软出品自动化神器Playwright(Playwright+Java)系列(四) 之 浏览器操作

    写在前面 今天是国庆节的最后一天,明天又要上班了,真的是感觉好像才开始放假一样,还是因为失恋没缓过来吗? 我的国庆七天 第1天,当了近半天的司机,陪家人去各大超市去购物,下午在家躺····· 第2-5 ...

  3. 2022-08-11-emo了

    layout: post cid: 7 title: emo了 slug: 7 date: 2022/08/11 10:14:00 updated: 2022/08/11 10:15:40 statu ...

  4. Java注解(4):一个真实的Elasticsearch案例

    昨天把拼了一半的注解+Elasticsearch积木放下了,因为东西太多了拼不好,还容易乱.休息了一晚上接着来. 接着昨天,创建elasticsearch文档注解(相当于数据表的注解): /** * ...

  5. Linux中CentOS 7版本安装JDK、Tomcat、MySQL、lezsz、maven软件详解

    软件安装 在Linux系统中,安装软件的方式主要有四种,这四种安装方式的特点如下: 安装方式 特点 二进制发布包安装 软件已经针对具体平台编译打包发布,只要解压,修改配置即可 rpm安装 软件已经按照 ...

  6. 消除两个inline-block元素之间的间隔

    发现问题 两个inline-block元素之间的间隔.如下图 期望 消除两个inline-block元素之间的间隔. 解决方法 1.父元素字体大小设置为0 间隔的形成是非元素标签形成的 /** 方案1 ...

  7. lombok下载和安装

    lombok是什么 第三方的组件:使用注解来简化类的编写,注解替换set/get/构造 注解: @setter @getter @NoArgsConstructor @AllArgsConstruct ...

  8. goroutine调度

    0.1.索引 https://blog.waterflow.link/articles/1662974432717 1.进程 一个进程包含可以由任何进程分配的公共资源.这些资源包括但不限于内存地址空间 ...

  9. 二、python基本数据类型

    一. 字面量 代码中,被写在代码中的固定的值,称之为字面量 Python常用6种值(数据)类型 字符串(string) :又称文本,是由任意数量的字符如中文.英文.各类符号.数字等组成.所以叫做字符的 ...

  10. GlusterFS常用维护操作命令

    GlusterFS常用维护操作命令 1.启动/关闭/查看glusterd服务 # /etc/init.d/glusterd start # /etc/init.d/glusterd stop # /e ...