linux内核启动过程2:保护模式执行流程
上一篇<<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:保护模式执行流程相关推荐
- linux内核启动过程5:启动用户空间
上一篇<<linux内核启动过程4:内核运行时>>分析到了内核进入运行时状态(不退出),本篇分析用户空间(用户层)的加载过程. 启动应用空间 进入kernel_init函数,在 ...
- linux内核启动过程3:内核初始化阶段
上一篇<<linux内核启动过程2:保护模式执行流程>>分析了保护模式启动过程以及bzImage的解压入口函数,本篇继续分析内核启动过程,从保护模式到C代码初始化. start ...
- linux文件系统启动流程,linux 内核启动过程以及挂载android 根文件系统的过程
转载 作者:汕头大学-黄珠唐 时间:2009 年10 月29 日 主要介绍linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析. 主要源 ...
- linux内核启动过程4:内核运行时
上一篇<<linux内核启动过程3:内核初始化阶段>>分析到了start_kernel执行流程,本篇继续内核切换到运行时状态. 内核运行时状态 内核初始化流程已经分析完成,如何 ...
- Linux内核启动过程概述
Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创造出自己的内核^_^ Linux的启动代码真的挺大,从汇 ...
- 简述arm linux内核启动流程,Linux内核启动过程和Bootloader(总述)
1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader是系统启 ...
- Linux内核启动过程和Bootloader(总述)
1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader ...
- Linux内核分析实验3——分析linux内核启动过程
本文大量内容引用自孟宁老师在<LINUX操作系统分析>课程中的内容 <Linux内核分析>MOOC课程 http://www.xuetangx.com/courses/cour ...
- 从linux内核启动,学习Linux内核启动过程:从start_kernel到init
一.实验步骤: 1:运行menuos: a)cd LinuxKernel/ b)qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd root ...
最新文章
- 为什么要完成量子计算机,我们为啥要量子计算机?
- 记录,再次运行vue项目报错POST http://127.0.0.1:8888/api/private/v1/login/login
- 2个vector如何合成一个_面试中如何做到不卑不亢,牢记2个要点
- 第三范式的作用_钟启泉:教学范式的转型,让一线教师面临三大挑战 | 头条
- 为什么总是封板又打开涨停_警惕!如果股票涨停板反复打开说明了什么?
- 八皇后(洛谷-P1219)
- 什么是大端序和小端序,为什么要有字节序
- c#推箱子小游戏代码_用C#制作推箱子小游戏
- solr学习笔记-linux下配置solr
- Asp.net常用的51个代码(非常实用)
- java接口深入理解_深入理解Java的接口和抽象类
- 【心电信号】基于matlab小波变换心电信号去噪【含Matlab源码 956期】
- 03.SpringBoot入门案例及详解
- 小程序滑动窗口的实现,固定第一列和第一行
- maya中英文对比_maya菜单中英文对照表
- iframe自动播放
- 你的伙伴对你最大会话_当你给朋友打电话的时候,最搞笑的一句回话是什么?...
- MySQL索引-视频+图文详解
- lamp环境实战操作建立完全属于自己的博客站点
- C++ windows下判断鼠标点击及获取像素点