上一篇<<linux内核压缩制作bzImage>>分析了bzImage制作流程,本篇继续分析内核启动过程,从实模式跳转到保护模式及后续执行流程。

protected_mode_jump

进入arch/x86/boot/pmjump.S:

/** void protected_mode_jump(u32 entrypoint, u32 bootparams);*/SYM_FUNC_START_NOALIGN(protected_mode_jump)...SYM_FUNC_END(protected_mode_jump)--------------------------------------------------------------------------------------------------------
/* 宏定义部分 */
/* SYM_FUNC_START_NOALIGN -- use for global functions, w/o alignment */
#ifndef SYM_FUNC_START_NOALIGN
#define SYM_FUNC_START_NOALIGN(name)                    \SYM_START(name, SYM_L_GLOBAL, SYM_A_NONE)
#endif#ifndef SYM_FUNC_END
/* the same as SYM_FUNC_END_ALIAS, see comment near SYM_FUNC_START */
#define SYM_FUNC_END(name)                              \SYM_END(name, SYM_T_FUNC)
#endif.../* SYM_ENTRY -- use only if you have to for non-paired symbols */
#ifndef SYM_ENTRY
#define SYM_ENTRY(name, linkage, align...)              \linkage(name) ASM_NL                            \align ASM_NL                                    \name:
#endif/* SYM_START -- use only if you have to */
#ifndef SYM_START
#define SYM_START(name, linkage, align...)              \SYM_ENTRY(name, linkage, align)
#endif/* SYM_END -- use only if you have to */
#ifndef SYM_END
#define SYM_END(name, sym_type)                         \.type name sym_type ASM_NL                      \.size name, .-name
#endif.../* SYM_L_* -- linkage of symbols */
#define SYM_L_GLOBAL(name)                      .globl name
#define SYM_L_WEAK(name)                        .weak name
#define SYM_L_LOCAL(name)                       /* nothing */.../* SYM_A_* -- align the symbol? */
#define SYM_A_ALIGN                             ALIGN
#define SYM_A_NONE                              /* nothing */

SYM_FUNC_START_NOALIGN(protected_mode_jump):

SYM_FUNC_START_NOALIGN(protected_mode_jump)||\/
SYM_START(protected_mode_jump, (.globl protected_mode_jump), SYM_A_NONE)||\/
SYM_ENTRY(protected_mode_jump,  (.globl protected_mode_jump), )||\/
.globl protected_mode_jump ;

解析得到.globl protected_mode_jump ; ,.globl将protected_mode_jump声明为外部程序可访问的标签(函数)

SYM_FUNC_END(protected_mode_jump):

SYM_FUNC_END(protected_mode_jump)||\/
SYM_END(protected_mode_jump, 2)||\/
.type protected_mode_jump 2 ; //标记protected_mode_jump为函数名
.size protected_mode_jump .-protected_mode_jump //得到protected_mode_jump函数占用字节大小

解析得到protected_mode_jump属于函数,及函数的占用字节大小

        movl    %edx, %esi              # Pointer to boot_params tablexorl    %ebx, %ebxmovw    %cs, %bxshll    $4, %ebxaddl    %ebx, 2fjmp     1f                      # Short jump to serialize on 386/486
1:movw    $__BOOT_DS, %cxmovw    $__BOOT_TSS, %dimovl    %cr0, %edxorb     $X86_CR0_PE, %dl        # Protected modemovl    %edx, %cr0# Transition to 32-bit mode.byte   0x66, 0xea              # ljmpl opcode
2:      .long   .Lin_pm32               # offset.word   __BOOT_CS               # segment/* Simple and small GDT entries for booting only:#define GDT_ENTRY_BOOT_CS       2
#define GDT_ENTRY_BOOT_DS       3
#define GDT_ENTRY_BOOT_TSS      4
#define __BOOT_CS               (GDT_ENTRY_BOOT_CS*8)
#define __BOOT_DS               (GDT_ENTRY_BOOT_DS*8)
#define __BOOT_TSS              (GDT_ENTRY_BOOT_TSS*8)*/

1. 将boot_params结构对象放入源索引寄存器,基地址寄存器归零(清空)
  2. 将代码段寄存器的地址放入基地址寄存器(16位),然后向左移动4位并将其添加到(2)定义的内存位置(转换为32位模式后要跳转的物理地址),跳转到1
  3. 将数据段放入cx、任务状态段放入di
  4. 设置保护允许位PE(Protedted Enable),用于启动保护模式
  5. 跳转到32位保护模式(.Lin_pm32),0x66允许混合16位和32位代码,0xea是跳转操作码

        .code32.section ".text32","ax"
SYM_FUNC_START_LOCAL_NOALIGN(.Lin_pm32) //定义函数.Lin_pm32# Set up data segments for flat 32-bit modemovl    %ecx, %dsmovl    %ecx, %esmovl    %ecx, %fsmovl    %ecx, %gsmovl    %ecx, %ss# The 32-bit code sets up its own stack, but this way we do have# a valid stack if some debugging hack wants to use it.addl    %ebx, %esp# Set up TR to make Intel VT happyltr     %di# Clear registers to allow for future extensions to the# 32-bit boot protocolxorl    %ecx, %ecxxorl    %edx, %edxxorl    %ebx, %ebxxorl    %ebp, %ebpxorl    %edi, %edi# Set up LDTR to make Intel VT happylldt    %cxjmpl    *%eax                   # Jump to the 32-bit entrypoint
SYM_FUNC_END(.Lin_pm32)

1. 使用32位编码,设置.text32段(允许执行)
  2. 设置相关数据段
  3. 设置用于调试的堆栈
  4. 清空通用寄存器
  5. 跳转到32位入口(跳转到(protected_mode_jump)第一个参数boot_params.hdr.code32_start地址),此时仍处于保护模式

bzImage处于保护模式后需要执行的部分

分析了几个小时,最后决定还是在这里加入bzImage处于保护模式后需要执行的部分。

之前的文章和上述过程讲的是vmlinux内的内核执行过程,也就是没有经过压缩的代码,可以通过protected_mode_jump跳转到startup_64(在arch/x86/kernel/head_64.S定义),参考vmlinux中的信息:

start address 0x0000000001000000Disassembly of section .text:ffffffff81000000 <startup_64>:

这里startup_64地址一致,然后参考head_64.S中startup_64内的一段话(在arch/x86/kernel/head_64.S定义):

We come here either directly from a 64bit bootloader, or from* arch/x86/boot/compressed/head_64.S.** We only come here initially at boot nothing else comes here.

也就是说bzImage需要解压(这里需要解压vmlinux.bin),然后才能执行startup_64,接下来先分析bzImage解压过程,然后再分析startup_64执行的内容。


进入arch/x86/boot/compressed/head_64.S,入口函数为startup_32:

/** Locally defined symbols should be marked hidden:*/.hidden _bss.hidden _ebss.hidden _got.hidden _egot.hidden _end__HEAD.code32
SYM_FUNC_START(startup_32)/** 32bit entry is 0 and it is ABI so immutable!* If we come here directly from a bootloader,* kernel(text+data+bss+brk) ramdisk, zero_page, command line* all need to be under the 4G limit.*/cld //cld  复位DF=0,向高地址增加cli   //禁止中断

startup_32属于从32位到64的过渡阶段,这里开始禁止中断

/** Calculate the delta between where we were compiled to run* at and where we were actually loaded at.  This can only be done* with a short local call on x86.  Nothing  else will tell us what* address we are running at.  The reserved chunk of the real-mode* data at 0x1e4 (defined as a scratch field) are used as the stack* for this calculation. Only 4 bytes are needed.*/
/* #define BP_scratch 484  */ /* offsetof(struct boot_params, scratch) */ leal    (BP_scratch+4)(%esi), %espcall    1f
1:      popl    %ebpsubl    $1b, %ebp/* Load new GDT with the 64bit segments using 32bit descriptor */leal    gdt(%ebp), %eaxmovl    %eax, 2(%eax)lgdt    (%eax)/* Load segment registers with our descriptors */movl    $__BOOT_DS, %eaxmovl    %eax, %dsmovl    %eax, %esmovl    %eax, %fsmovl    %eax, %gsmovl    %eax, %ss/* setup a stack and make sure cpu supports long mode. */leal    boot_stack_end(%ebp), %esp

这里的部分地址需要重新计算(通过在实模式中0x1e4地址预留的4个字节内完成),设置相关数据段,设置堆栈,确保cpu支持长模式

         call    verify_cpu testl   %eax, %eaxjnz     .Lno_longmode

校验cpu是否支持长模式,如果不支持执行.Lno_longmode。示例机器支持长模式,进行向下看

** Compute the delta between where we were compiled to run at* and where the code will actually run at.** %ebp contains the address we are loaded at by the boot loader and %ebx* contains the address where we should move the kernel image temporarily* for safe in-place decompression.*/#ifdef CONFIG_RELOCATABLEmovl    %ebp, %ebx

%ebp包含引导加载程序和%ebx加载的地址

#ifdef CONFIG_EFI_STUB
/** If we were loaded via the EFI LoadImage service, startup_32 will be at an* offset to the start of the space allocated for the image. efi_pe_entry will* set up image_offset to tell us where the image actually starts, so that we* can use the full available buffer.*      image_offset = startup_32 - image_base* Otherwise image_offset will be zero and has no effect on the calculations.*/subl    image_offset(%ebp), %ebx
#endif

如果是通过EFI LoadImage服务加载的,那么startup_32将在偏移到为image分配的空间的起点


/* #define BP_kernel_alignment 560 /* offsetof(struct boot_params, hdr.kernel_alignment) */#define CONFIG_PHYSICAL_START 0x1000000
#define CONFIG_PHYSICAL_ALIGN 0x200000/* Physical address where kernel should be loaded. */
#define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \+ (CONFIG_PHYSICAL_ALIGN - 1)) \& ~(CONFIG_PHYSICAL_ALIGN - 1))movl    BP_kernel_alignment(%esi), %eaxdecl    %eaxaddl    %eax, %ebxnotl    %eaxandl    %eax, %ebxcmpl    $LOAD_PHYSICAL_ADDR, %ebxjae     1f
#endifmovl    $LOAD_PHYSICAL_ADDR, %ebx

如果是EFI引导,需要计算出实际物理地址,否则直接把物理地址放入基地址(寄存器)

/** Prepare for entering 64 bit mode*//* Enable PAE mode */movl    %cr4, %eaxorl     $X86_CR4_PAE, %eaxmovl    %eax, %cr4

启用PAE模式

/** Build early 4G boot pagetable*//** If SEV is active then set the encryption mask in the page tables.* This will insure that when the kernel is copied and decompressed* it will be done so encrypted.*/call    get_sev_encryption_bitxorl    %edx, %edxtestl   %eax, %eaxjz      1f    subl    $32, %eax       /* Encryption bit is always above bit 31 */bts     %eax, %edx      /* Set encryption mask for page tables */1:/* Initialize Page tables to 0 */leal    pgtable(%ebx), %edixorl    %eax, %eaxmovl    $(BOOT_INIT_PGT_SIZE/4), %ecxrep     stosl/* Build Level 4 */leal    pgtable + 0(%ebx), %edileal    0x1007 (%edi), %eaxmovl    %eax, 0(%edi)addl    %edx, 4(%edi)/* Build Level 3 */leal    pgtable + 0x1000(%ebx), %edileal    0x1007(%edi), %eaxmovl    $4, %ecx

构建早期4G页表,为页表设置加密掩码

/* Enable the boot page tables */leal    pgtable(%ebx), %eaxmovl    %eax, %cr3

启动页表,cr3存放页目录表物理内存基地址

 /* Enable Long mode in EFER (Extended Feature Enable Register) */movl    $MSR_EFER, %ecxrdmsrbtsl    $_EFER_LME, %eaxwrmsr

在EFER(扩展功能启用寄存器)中启用长模式

 /* After gdt is loaded */xorl    %eax, %eaxlldt    %axmovl    $__BOOT_TSS, %eaxltr     %ax/** Setup for the jump to 64bit mode** When the jump is performend we will be in long mode but* in 32bit compatibility mode with EFER.LME = 1, CS.L = 0, CS.D = 1* (and in turn EFER.LMA = 1).  To jump into 64bit mode we use* the new gdt/idt that has __KERNEL_CS with CS.L = 1.* We place all of the values on our mini stack so lret can* used to perform that far jump.*/leal    startup_64(%ebp), %eax

进入64位模式:

.code64.org 0x200
SYM_CODE_START(startup_64)/** 64bit entry is 0x200 and it is ABI so immutable!* We come here either from startup_32 or directly from a* 64bit bootloader.* If we come here from a bootloader, kernel(text+data+bss+brk),* ramdisk, zero_page, command line could be above 4G.* We depend on an identity mapped page table being provided* that maps our entire kernel(text+data+bss+brk), zero page* and command line.*/cldcli/* Setup data segments. */xorl    %eax, %eaxmovl    %eax, %dsmovl    %eax, %esmovl    %eax, %ssmovl    %eax, %fsmovl    %eax, %gs

由于已经进入到新的地址(模式),再次设置了段寄存器

 /** Compute the decompressed kernel start address.  It is where* we were loaded at aligned to a 2M boundary. %rbp contains the* decompressed kernel start address.** If it is a relocatable kernel then decompress and run the kernel* from load address aligned to 2MB addr, otherwise decompress and* run the kernel from LOAD_PHYSICAL_ADDR** We cannot rely on the calculation done in 32-bit mode, since we* may have been invoked via the 64-bit entry point.*//* Start with the delta to where the kernel will run at. */...pushq   %rsimovq    %rsi, %rdi              /* real mode address */call    paging_preparepopq    %rsi/* Save the trampoline address in RCX */movq    %rax, %rcx/* Save the trampoline address in RCX */movq    %rax, %rcx/** Load the address of trampoline_return() into RDI.* It will be used by the trampoline to return to the main code.*/leaq    trampoline_return(%rip), %rdi/* Switch to compatibility mode (CS.L = 0 CS.D = 1) via far return */pushq   $__KERNEL32_CSleaq    TRAMPOLINE_32BIT_CODE_OFFSET(%rax), %raxpushq   %raxlretq
trampoline_return:/* Restore the stack, the 32-bit trampoline uses its own stack */leaq    boot_stack_end(%rbx), %rsppushq   %rsileaq    top_pgtable(%rbx), %rdicall    cleanup_trampolinepopq    %rsi/* Zero EFLAGS */pushq   $0popfq...pushq   %rsileaq    (_bss-8)(%rip), %rsileaq    (_bss-8)(%rbx), %rdimovq    $_bss /* - $startup_32 */, %rcxshrq    $3, %rcxstdrep     movsqcldpopq    %rsi.../** The GDT may get overwritten either during the copy we just did or* during extract_kernel below. To avoid any issues, repoint the GDTR* to the new copy of the GDT.*/leaq    gdt64(%rbx), %raxleaq    gdt(%rbx), %rdxmovq    %rdx, 2(%rax)lgdt    (%rax)/** Jump to the relocated address.*/leaq    .Lrelocated(%rbx), %raxjmp     *%rax
SYM_CODE_END(startup_64)

重载地址,设置分页,将压缩后的内核拷贝到栈尾解压,跳转到重新定位的地址( arch/x86/kernel/head_64.S中的入口,startup_64)。

linux内核启动过程2:保护模式执行流程相关推荐

  1. linux内核启动过程5:启动用户空间

    上一篇<<linux内核启动过程4:内核运行时>>分析到了内核进入运行时状态(不退出),本篇分析用户空间(用户层)的加载过程. 启动应用空间 进入kernel_init函数,在 ...

  2. linux内核启动过程3:内核初始化阶段

    上一篇<<linux内核启动过程2:保护模式执行流程>>分析了保护模式启动过程以及bzImage的解压入口函数,本篇继续分析内核启动过程,从保护模式到C代码初始化. start ...

  3. linux文件系统启动流程,linux 内核启动过程以及挂载android 根文件系统的过程

    转载 作者:汕头大学-黄珠唐 时间:2009 年10 月29 日 主要介绍linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析. 主要源 ...

  4. linux内核启动过程4:内核运行时

    上一篇<<linux内核启动过程3:内核初始化阶段>>分析到了start_kernel执行流程,本篇继续内核切换到运行时状态. 内核运行时状态 内核初始化流程已经分析完成,如何 ...

  5. Linux内核启动过程概述

    Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创造出自己的内核^_^ Linux的启动代码真的挺大,从汇 ...

  6. 简述arm linux内核启动流程,Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader是系统启 ...

  7. Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述     一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader ...

  8. Linux内核分析实验3——分析linux内核启动过程

    本文大量内容引用自孟宁老师在<LINUX操作系统分析>课程中的内容 <Linux内核分析>MOOC课程 http://www.xuetangx.com/courses/cour ...

  9. 从linux内核启动,学习Linux内核启动过程:从start_kernel到init

    一.实验步骤: 1:运行menuos: a)cd LinuxKernel/ b)qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd root ...

最新文章

  1. 为什么要完成量子计算机,我们为啥要量子计算机?
  2. 记录,再次运行vue项目报错POST http://127.0.0.1:8888/api/private/v1/login/login
  3. 2个vector如何合成一个_面试中如何做到不卑不亢,牢记2个要点
  4. 第三范式的作用_钟启泉:教学范式的转型,让一线教师面临三大挑战 | 头条
  5. 为什么总是封板又打开涨停_警惕!如果股票涨停板反复打开说明了什么?
  6. 八皇后(洛谷-P1219)
  7. 什么是大端序和小端序,为什么要有字节序
  8. c#推箱子小游戏代码_用C#制作推箱子小游戏
  9. solr学习笔记-linux下配置solr
  10. Asp.net常用的51个代码(非常实用)
  11. java接口深入理解_深入理解Java的接口和抽象类
  12. 【心电信号】基于matlab小波变换心电信号去噪【含Matlab源码 956期】
  13. 03.SpringBoot入门案例及详解
  14. 小程序滑动窗口的实现,固定第一列和第一行
  15. maya中英文对比_maya菜单中英文对照表
  16. iframe自动播放
  17. 你的伙伴对你最大会话_当你给朋友打电话的时候,最搞笑的一句回话是什么?...
  18. MySQL索引-视频+图文详解
  19. lamp环境实战操作建立完全属于自己的博客站点
  20. C++ windows下判断鼠标点击及获取像素点

热门文章

  1. 再招一万人!疯了。。。
  2. Nginx的这些妙用,你肯定有不知道的!
  3. 中台实践:新汽车行业的业务、技术和平台转型
  4. springboot2.0集成activiti modeler
  5. 浅谈分布式消息技术 Kafka
  6. VS 2017 C++查看变量
  7. django models 文件夹
  8. PB开发境界 多个DW进行update
  9. 会计丑闻之后 东芝“迎来”第五次延交财报
  10. hdu1598(并查集)