当前位置: 首页 > news >正文

Linux源码阅读: Linux内核启动的基本流程

目录

从硬件上电到BIOS

从BIOS到Bootloader

BootLoader加载流程

main

Reference

进程管理


从硬件上电到BIOS

我们首先需要说的是实模式,实模式让所有软件访问到的地址都是最真实的物理地址。同时,软件可以不受限制的操作IO和内存。与之相对的是保护模式:进程访问到的全部都是虚拟地址,使用页表等维护到物理地址的映射,从而对内存和IO设备进行保护,显然这更加的安全。同时保证了扩展性和灵活性(在不同的进程下,他们使用的同一个虚拟地址可以访问到不同的物理地址)。

现在,摁下电源键,主板开始reset我们的硬件状态,将CPU进行初始化。对于使用Intel的芯片,他们会首先开启实模式:此时此刻使用内存段管理0 - 0xFFFFF。CPU自身被初始化为0XFFFF0(CS:IP = 0xFFFF: 0x0000)

从BIOS到Bootloader

BIOS的启动和运行程序放在了CPU上电后初始化的位置。上面,我提到了CPU自身被初始化为0XFFFF0(CS:IP = 0xFFFF: 0x0000)。说明我们的BIOS程序放在了这里。现在,程序开始进行硬件自检(查看硬件有没有问题)。随后将会选择一个启动设备,即扫描存在启动代码的扇区,交给其中的Bootloader来初始化中断向量和中断服务程序。现在在将boot.img放到一个0x7c00(暂时放到这里)

BootLoader加载流程

boot.img就是boot.S编译的结果,安装在启动盘的第一个扇区,即MBR。由于空间大小有限,故只是起到了一个引导的作用。现在,我们加载好内核后启动main函数:即初始化控制台,计算模块的基地址,设置root设备,读取grub引导文件以及加载模块。之后就会开始创建0号进程等开始运行了。

main

main函数将会进行一系列初始化,之后才会开启新的中断。从而使得内核模式进行真正的运行:

  • 为0号进程建立内核态的堆栈

  • 清空eflags今存其

  • 调用setup_idt填充idt

  • 把bios获取到的参数传递给第一个页框进行保存

  • 使用GDT和IDT表填充寄存器

  • 建立终端,让用户可以开始交互

在Linux的核心启动函数是:start_kernel

init/main.c

asmlinkage __visible __init __no_sanitize_address __noreturn __no_stack_protector
void start_kernel(void)  // 内核启动入口函数
{char *command_line;   // 存储命令行参数的指针char *after_dashes;   // 存储处理后的命令行参数指针
​set_task_stack_end_magic(&init_task);  // 设置初始任务的栈结束魔数,检测栈溢出smp_setup_processor_id();                // 设置对称多处理系统的 CPU IDdebug_objects_early_init();              // 进行早期调试对象的初始化init_vmlinux_build_id();                 // 初始化内核版本信息和编译时间
​cgroup_init_early();                     // 早期初始化控制组(cgroup)local_irq_disable();                     // 禁用本地中断early_boot_irqs_disabled = true;        // 标记早期引导中断已禁用
​boot_cpu_init();                         // 初始化引导 CPUpage_address_init();                     // 初始化页地址pr_notice("%s", linux_banner);           // 输出内核版本信息early_security_init();                   // 初始化安全相关配置setup_arch(&command_line);               // 进行架构相关设置setup_boot_config();                     // 设置引导配置setup_command_line(command_line);        // 解析命令行参数setup_nr_cpu_ids();                      // 设置 CPU ID 数量setup_per_cpu_areas();                   // 初始化每个 CPU 的内存区域smp_prepare_boot_cpu();                  // 准备引导 CPU 的架构特定设置early_numa_node_init();                  // 进行早期 NUMA 节点初始化boot_cpu_hotplug_init();                 // 初始化引导 CPU 热插拔支持
​pr_notice("Kernel command line: %s\n", saved_command_line);  // 打印内核命令行参数jump_label_init();                       // 初始化跳跃标签parse_early_param();                     // 解析早期参数
​// 如果还记得如何调试Linux内核的话after_dashes = parse_args("Booting kernel", // 解析引导时的命令行参数static_command_line, __start___param,__stop___param - __start___param,-1, -1, NULL, &unknown_bootoption);print_unknown_bootoptions();             // 打印未识别的引导选项
​if (!IS_ERR_OR_NULL(after_dashes))      // 如果解析成功parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,NULL, set_init_arg);    // 设置初始化参数
​if (extra_init_args)                    // 如果有额外的初始化参数parse_args("Setting extra init args", extra_init_args,NULL, 0, -1, -1, NULL, set_init_arg);  // 设置额外初始化参数
​random_init_early(command_line);        // 进行早期随机数生成器初始化
​setup_log_buf(0);                       // 设置日志缓冲区vfs_caches_init_early();                // 初始化虚拟文件系统缓存sort_main_extable();                    // 排序主异常表trap_init();                            // 初始化异常处理mm_core_init();                         // 初始化内存管理核心poking_init();                          // 初始化 poking 机制ftrace_init();                          // 初始化跟踪功能
​early_trace_init();                     // 进行早期跟踪初始化
​sched_init();                           // 初始化调度器
​if (WARN(!irqs_disabled(),                // 检查中断是否意外启用"Interrupts were enabled *very* early, fixing it\n"))local_irq_disable();                // 如果启用,则禁用本地中断
​radix_tree_init();                      // 初始化基数树maple_tree_init();                      // 初始化maple tree// 这是Linux新引入的数据结构
​housekeeping_init();                    // 初始化系统管理相关内容
​workqueue_init_early();                 // 初始化工作队列rcu_init();                             // 初始化 RCU
​trace_init();                           // 初始化跟踪事件
​if (initcall_debug)                     // 如果启用初始化调用调试initcall_debug_enable();            // 启用调试
​context_tracking_init();                // 初始化上下文跟踪early_irq_init();                       // 早期初始化中断init_IRQ();                            // 初始化中断处理tick_init();                            // 初始化定时器rcu_init_nohz();                       // 初始化无时钟 RCUinit_timers();                         // 初始化定时器系统srcu_init();                           // 初始化 SRCUhrtimers_init();                        // 初始化高分辨率定时器softirq_init();                         // 初始化软中断timekeeping_init();                     // 初始化时间keeping系统time_init();                           // 完成时间初始化
​random_init();                          // 进行随机数生成器初始化
​kfence_init();                          // 初始化内存保护机制boot_init_stack_canary();               // 初始化栈金丝雀
​perf_event_init();                      // 初始化性能事件监控profile_init();                         // 初始化性能分析配置
​call_function_init();                   // 初始化函数调用机制WARN(!irqs_disabled(),                  // 检查中断状态"Interrupts were enabled early\n");
​early_boot_irqs_disabled = false;      // 允许后续启用中断local_irq_enable();                     // 启用本地中断
​kmem_cache_init_late();                 // 延迟初始化内存缓存
​console_init();                         // 初始化控制台以输出信息if (panic_later)                        // 如果有紧急情况panic("Too many boot %s vars at `%s'", panic_later,panic_param);                 // 触发内核恐慌
​lockdep_init();                        // 初始化锁依赖检查
​locking_selftest();                     // 自检锁机制
​
#ifdef CONFIG_BLK_DEV_INITRDif (initrd_start && !initrd_below_start_ok &&  // 检查 initrdpage_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;                   // 如果被覆盖则禁用 initrd}
#endif
​setup_per_cpu_pageset();                // 设置每个 CPU 的页面集合numa_policy_init();                     // 初始化 NUMA 策略acpi_early_init();                      // 早期初始化 ACPIif (late_time_init)                     // 如果有延迟时间初始化late_time_init();                   // 执行延迟时间初始化sched_clock_init();                     // 初始化调度时钟calibrate_delay();                      // 校准延迟
​arch_cpu_finalize_init();               // 完成架构相关的 CPU 初始化
​pid_idr_init();                         // 初始化 PID IDRanon_vma_init();                        // 初始化匿名虚拟内存
​
#ifdef CONFIG_X86if (efi_enabled(EFI_RUNTIME_SERVICES))  // 检查 EFI 运行时服务是否启用efi_enter_virtual_mode();           // 进入 EFI 虚拟模式
#endif
​thread_stack_cache_init();              // 初始化线程栈缓存cred_init();                            // 初始化凭证管理fork_init();                            // 初始化进程分叉机制proc_caches_init();                     // 初始化进程缓存uts_ns_init();                          // 初始化 UTS 命名空间key_init();                             // 初始化密钥管理security_init();                        // 初始化安全管理dbg_late_init();                        // 延迟初始化调试相关内容net_ns_init();                          // 初始化网络命名空间vfs_caches_init();                      // 初始化虚拟文件系统缓存pagecache_init();                       // 初始化页面缓存signals_init();                         // 初始化信号管理seq_file_init();                        // 初始化序列文件支持proc_root_init();                       // 初始化进程根目录nsfs_init();                            // 初始化命名空间文件系统pidfs_init();                           // 初始化 PID 文件系统cpuset_init();                          // 初始化 CPU 集cgroup_init();                          // 初始化控制组taskstats_init_early();                 // 早期初始化任务统计delayacct_init();                       // 初始化延迟账户
​acpi_subsystem_init();                  // 初始化 ACPI 子系统arch_post_acpi_subsys_init();           // 执行架构特定的 ACPI 后初始化kcsan_init();                           // 初始化 KCSAN(内存竞争检测)
​rest_init();                            // 执行后续初始化,开始内核主循环
}
​

当然还有标记有没有关闭本地中断的检查:

/** Debug helper: via this flag we know that we are in 'early bootup code'* where only the boot processor is running with IRQ disabled.  This means* two things - IRQ must not be enabled before the flag is cleared and some* operations which are not allowed with IRQ disabled are allowed while the* flag is set.*/
bool early_boot_irqs_disabled __read_mostly;

更加详细的注释我放在Reference了

Reference

asmlinkage __visible __init __no_sanitize_address __noreturn __no_stack_protector
void start_kernel(void)
  • asmlinkage:指示此函数的参数通过栈传递,通常用于系统调用。

  • __visible:表示该函数对外可见。

  • __init:表示函数只在初始化期间使用,之后会被释放以节省内存。

  • __no_sanitize_address:告诉编译器不要对该函数进行地址消毒检查。

  • __noreturn:指示该函数不会返回,通常是因为它会调用 exec() 或进入无限循环。

  • __no_stack_protector:表示不启用栈保护机制。

c复制代码char *command_line;
char *after_dashes;

定义了两个指针:command_line 用于存储内核启动时的命令行参数,after_dashes 用于存储在命令行参数中处理后的部分。

set_task_stack_end_magic(&init_task);

设置初始任务的栈结束魔数,帮助检测栈溢出。魔数是一种特殊值,用于标识栈的边界。

smp_setup_processor_id();

针对对称多处理(SMP)系统设置 CPU ID,为每个 CPU 分配唯一的标识符。

debug_objects_early_init();

进行早期的调试对象初始化,确保调试工具可以使用,用于后续的内存和对象管理。

init_vmlinux_build_id();

初始化内核的版本信息(如编译时间和版本号),便于在调试时识别使用的内核版本。

cgroup_init_early();

早期初始化控制组(cgroup),用于管理和限制资源使用,如 CPU 和内存。

local_irq_disable();
early_boot_irqs_disabled = true;

禁用本地中断,防止在初始化期间发生中断,标记早期引导中断已禁用。

接下来是进行各个子系统的初始化,包括:

boot_cpu_init();

初始化引导 CPU,包括设置其状态和特性。

page_address_init();

初始化页地址,设置虚拟地址到物理地址的映射。

pr_notice("%s", linux_banner);

输出内核版本信息,通常由 linux_banner 定义。

early_security_init();

进行安全相关的初始化,确保引导过程中的安全性。

setup_arch(&command_line);

进行与架构相关的设置,处理 command_line

setup_boot_config();
setup_command_line(command_line);

设置引导配置和命令行参数。

setup_nr_cpu_ids();
setup_per_cpu_areas();

初始化 CPU ID 数量和每个 CPU 的区域,准备多处理器支持。

smp_prepare_boot_cpu();

为引导 CPU 准备架构特定的设置。

early_numa_node_init();

进行早期 NUMA(非统一内存访问)节点初始化。

boot_cpu_hotplug_init();

初始化引导 CPU 热插拔功能,允许动态管理 CPU。

接下来,打印内核命令行参数并进行早期参数解析:

pr_notice("Kernel command line: %s\n", saved_command_line);
jump_label_init();
parse_early_param();

显示当前的内核命令行,初始化跳跃标签(用于动态调试),解析早期参数。

after_dashes = parse_args("Booting kernel",static_command_line, __start___param,__stop___param - __start___param,-1, -1, NULL, &unknown_bootoption);

解析命令行参数并处理,查找未知选项。

print_unknown_bootoptions();

打印未识别的引导选项,以便开发者调试。

if (!IS_ERR_OR_NULL(after_dashes))parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,NULL, set_init_arg);

如果解析成功,则设置初始化参数。

接下来的代码块继续进行早期的随机数生成、日志缓冲区初始化、虚拟文件系统缓存初始化等,确保内核启动时各个子系统处于良好状态。

sched_init();

初始化调度器,准备管理任务的执行。

local_irq_disable();

再次禁用本地中断,确保在初始化期间不会发生中断。

rcu_init();

初始化 RCU(读-复制-更新)机制,确保高效的并发访问。

tick_init();
  • 初始化系统定时器(tick),这为内核提供了时间管理的基础,允许内核跟踪时间流逝并管理任务的调度。

timekeeping_init();
  • 进行时间keeping系统的初始化。这包括设置系统时钟、处理闰年和闰秒等,以确保系统时间的准确性。

time_init();
  • 完成时间相关的初始化,设置系统的基准时间,确保其他模块可以使用正确的时间信息。

perf_event_init();
  • 初始化性能事件监控。这个机制允许内核和用户空间程序监控硬件性能计数器,以进行性能分析和调优。

profiie_init();
  • 初始化性能分析相关的配置和数据结构。这为后续的性能分析提供支持。

进程管理

fork_init();
  • 初始化进程分叉相关的设置,准备处理新的进程创建。这个函数为系统的多任务处理打下基础。

proc_caches_init();
  • 初始化与进程相关的缓存,以提高进程管理的效率。这涉及到为进程信息、文件描述符等分配缓存,以减少内存访问延迟。

cred_init();
  • 初始化进程的凭证(credentials)管理系统。凭证用于管理进程的身份和权限,确保安全性。

signals_init();
  • 初始化信号处理系统,使内核能够有效地处理进程间通信和信号通知。

其他参考

Linux初始化 - 知乎 (zhihu.com)

[Introducing maple trees LWN.net]


http://www.mrgr.cn/news/50390.html

相关文章:

  • 动力学控制过程
  • 花生米清洗污水处理设备核心处理方法
  • 19 Shell Script awk命令
  • 华为OD机试真题---热点网站统计
  • C# Attribute 介绍
  • 大模型应用开发过程中主流架构模式——大模型+多个小模型
  • Windows 11开发
  • DDR系列之一: 内存的种类
  • 【前端】浏览器中的hevc视频播放库汇总,无需wasm
  • 扩散模型训练方法一直错了!
  • 计算机和网络
  • Redis拒绝连接问题分析与解决方案
  • 黑龙江等保测评:APP安全性的重要性与实施策略
  • XPM_CDC_ARRAY_SINGLE
  • Java比较两个Excel是否内容一致
  • 【接口限流】java中springboot实现接口限流防抖处理(JUC注解版)
  • 【Java小白系列课】-01-Java环境安装-变量
  • Palo Alto Networks Expedition 未授权SQL注入漏洞复现(CVE-2024-9465)
  • 网络资源模板--Android Studio 实现宿舍管理系统App
  • MYSQL数据库和oracle数据库的详细对比,该怎么选择?