• Linux内核分析:Linux内核启动流程分析


    (注:本文参考资料:朱有鹏嵌入式课程、大神博客。本文为个人学习记录,如有错误,欢迎指正。内核版本:九鼎公司移植的2.6.35.7

    1. Linux内核自解压过程

    uboot完成系统引导以后,执行环境变量bootm中的命令;即,将Linux内核调入内存中并调用do_bootm函数启动内核,跳转至kernel的起始位置。如果内核没有被压缩,则直接启动;如果内核被压缩过,则需要进行解压,被压缩过的kernel头部有解压程序。

    压缩过的kernel入口第一个文件源码位置在/kernel/arch/arm/boot/compressed/head.S。它将调用decompress_kernel()函数进行解压,解压完成后,打印出信息“Uncompressing Linux...done,booting the kernel”。解压缩完成后,调用gunzip()函数(或unlz4()、或bunzip2()、或unlz())将内核放于指定位置,开始启动内核。

    P.S.:内核格式类型详见

    2. Linux内核启动准备阶段

    由内核链接脚本/kernel/arch/arm/kernel/vmlinux.lds可知,内核入口函数为stext(/kernel/arch/arm/kernel/head.S)。内核解压完成后,解压缩代码调用stext函数启动内核。

    P.S.:内核链接脚本vmlinux.lds在内核配置过程中产生,由/kernel/arch/arm/kernel/vmlinux.lds.S文件生成。原因是,内核链接脚本为适应不同平台,有条件编译的需求,故由一个汇编文件来完成链接脚本的制作。

    ENTRY(stext)
    setmodePSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode@ and irqs disabled
    mrcp15, 0, r9, c0, c0            @ 获得处理器ID,并存储在r9寄存器中
    bl__lookup_processor_type        @ 结果返回:描述处理器结构体的地址 r5=procinfo ,处理器ID号 r9=cpuid
    movsr10, r5                      @ invalid processor (r5=0)?判断内核是否支持该处理器
    beq__error_p                     @ yes, error 'p'
    bl__lookup_machine_type          @结果返回:描述机器(开发板)的结构体地址  r5=machinfo
    movsr8, r5                       @ invalid machine (r5=0)?判断内核是否支持该机器(开发板)
    beq__error_a                     @ yes, error 'a'
    bl__vet_atags                    @检查uboot给内核的传参ATAGS格式是否正确
    bl__create_page_tables           @建立虚拟地址映射页表
    
    ldrr13, __switch_data            @ address to jump to after

    (1)关闭IRQ、FIQ中断,进入SVC模式。调用setmode宏实现;

    (2)校验处理器ID,检验内核是否支持该处理器;若不支持,则停止启动内核。调用__lookup_processor_type函数实现;

    (3)校验机器码,检验内核是否支持该机器;若不支持,则停止启动内核。调用__lookup_machine_type函数实现;

    (4)检查uboot向内核传参ATAGS格式是否正确,调用__vet_atars函数实现;

    (5)建立虚拟地址映射页表。此处建立的页表为粗页表,在内核启动前期使用。Linux对内存管理有更精细的要求,随后会重新建立更精细的页表。调用__create_page_tables函数实现。

    (6)跳转执行__switch_data函数,其中调用__mmap_switched完成最后的准备工作。

            1)复制数据段、清除bss段,目的是构建C语言运行环境;

            2)保存处理器ID号、机器码、uboot向内核传参地址;

            3)b   start_kernel跳转至内核初始化阶段。

    __switch_data:
    .long__mmap_switched
    ..........................................................
    __mmap_switched:
    adrr3, __switch_data + 4
    
    ldmiar3!, {r4, r5, r6, r7}
    cmpr4, r5@ Copy data segment if needed
    1:cmpner5, r6
    ldrnefp, [r4], #4
    strnefp, [r5], #4
    bne1b
    
    movfp, #0@ Clear BSS (and zero fp)
    1:cmpr6, r7
    strccfp, [r6],#4
    bcc1b
    
     ARM(ldmiar3, {r4, r5, r6, r7, sp})
     THUMB(ldmiar3, {r4, r5, r6, r7})
     THUMB(ldrsp, [r3, #16])
    strr9, [r4]@ Save processor ID
    strr1, [r5]@ Save machine type
    strr2, [r6]@ Save atags pointer
    bicr4, r0, #CR_A@ Clear 'A' bit
    stmiar7, {r0, r4}@ Save control register values
    bstart_kernel
    ENDPROC(__mmap_switched)

    3. Linux内核初始化阶段

    此阶段从start_kernel函数开始。start_kernel函数是所有Linux平台进入系统内核初始化的入口函数。它的主要工作是完成剩余与硬件平台相关的初始化工作,在进行一系列与内核相关的初始化之后,调用第一个用户进程init并等待其执行。至此,整个内核启动完成。

    3.1 start_kernel函数的主要工作

    start_kernel函数主要完成内核相关的初始化工作。具体包括以下部分:

    (1)内核架构 、通用配置相关初始化 

    (2) 内存管理相关初始化

    (3)进程管理相关初始化 

    (4)进程调度相关初始化

    (5)网络子系统管理

    (6)虚拟文件系统

    (7)文件系统 

    start_kernel函数详解。

    3.2 start_kernel函数流中的关键函数

    (1)setup_arch(&command_line)函数

    内核架构相关的初始化函数,是非常重要的一个初始化步骤。其中,包含了处理器相关参数的初始化、内核启动参数(tagged list)的获取和前期处理、内存子系统的早期初始化。

    command_line实质是uboot向内核传递的命令行启动参数,即uboot中环境变量bootargs的值。若uboot中bootargs的值为空,command_line = default_command_line,即为内核中的默认命令行参数,其值在.config文件中配置,对应CONFIG_CMDLINE配置项。

    (2)setup_command_line、parse_early_param以及parse_args函数

    这些函数都是在完成命令行参数的解析、保存。譬如,cmdline = console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3;解析为一下四个参数:

    • console=ttySAC2,115200      //指定控制台的串口设备号,及其波特率
    • root=/dev/mmcblk0p2 rw    //指定根文件系统rootfs的路径
    • init=/linuxrc                           //指定第一个用户进程init的路径
    • rootfstype=ext3                    //指定根文件系统rootfs的类型

    (3)sched_init函数

    初始化进程调度器,创建运行队列,设置当前任务的空线程。

    (4)rest_init函数

    rest_init函数的主要工作如下:

        1)调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd。kernel_init线程中调用prepare_namespace函数挂载根文件系统rootfs;然后调用init_post函数,执行根文件系统rootfs下的第一个用户进程init。用户进程有4个备选方案,若command_line中init的路径错误,则会执行备用方案。第一备用:/sbin/init,第二备用:/etc/init,第三备用:/bin/init,第四备用:/bin/sh。

        2)调用schedule函数开启内核调度系统;

        3)调用cpu_idle函数,启动空闲进程idle,完成内核启动。

  • 相关阅读:
    Python学习札记(十五) 高级特性1 切片
    LeetCode Longest Substring Without Repeating Characters
    Python学习札记(十四) Function4 递归函数 & Hanoi Tower
    single number和变体
    tusen 刷题
    实验室网站
    leetcode 76. Minimum Window Substring
    leetcode 4. Median of Two Sorted Arrays
    leetcode 200. Number of Islands 、694 Number of Distinct Islands 、695. Max Area of Island 、130. Surrounded Regions 、434. Number of Islands II(lintcode) 并查集 、178. Graph Valid Tree(lintcode)
    刷题注意事项
  • 原文地址:https://www.cnblogs.com/linfeng-learning/p/9285547.html
Copyright © 2020-2023  润新知