/*
* relocate_kernel.S - put the kernel image in place to boot
* Copyright (C) 2002-2004 Eric Biederman <ebiederm@xmission.com>
*
* This source code is licensed under the GNU General Public License,
* Version 2. See the file COPYING for more details.
*/
#include <linux/linkage.h>
#include <asm/page.h>
#include <asm/kexec.h>
/*
* Must be relocatable PIC code callable as a C function
汇编存根代码执行下面的操作:
*自栈中读取参数,并将它们存储到寄存器中,然后禁用中断。
*使用以参数形式传递给自己的页地址(?????),在页的末端设置一个栈。
*将新内核映像的起始地址存储到栈中,以使得存根代码的返回自动将系统引导到新的内核映像。
*设置 cr0 寄存器的适当位来禁用内存分页。
*将页目录基址寄存器 cr4 重设为 0。
*清空快表(Translation Lookaside Buffers,TLB)。
*将所有内核映像页拷贝到最终目标页。
*再次清空 TLB。
*将除了栈指针寄存器 esp(因为它指向容纳新内核起始地址的栈)以外的所有寄存器重设为 0。
*自存根代码“返回”。自动将系统引导到新内核。
这一系列工作完成后,新内核获得控制权,然后系统正常引导起来//此处注释来源于https://www.ibm.com/developerworks/cn/linux/l-kexec/
*/
#define PTR(x) (x << 2)
#define PAGE_ALIGNED (1 << PAGE_SHIFT)
#define PAGE_ATTR 0x63 /* _PAGE_PRESENT|_PAGE_RW|_PAGE_ACCESSED|_PAGE_DIRTY */
#define PAE_PGD_ATTR 0x01 /* _PAGE_PRESENT */
.text
.align PAGE_ALIGNED
.globl relocate_kernel
relocate_kernel:
movl 8(%esp), %ebp /* list of pages */
#ifdef CONFIG_X86_PAE
//略去PAE部分
#else
/* map the control page at its virtual address */
page_list->+----------------------------------+ <--ebp
0 | PA_CONTROL_PAGE |
|-----------------------------------|
1 | VA_CONTROL_PAGE |
|-----------------------------------|
2 | PA_PGD |
|-----------------------------------|
3 | VA_PGD |
|-----------------------------------|
4 | PA_PTE_0 |
|-----------------------------------|
5 | VA_PTE_0 |
|-----------------------------------|
6 | PA_PTE_1 |
|-----------------------------------|
7 | VA_PTE_1 |
+---------------------------------+
//其实就是将PA_PTE_0和PA_PTE_1的地址存到页全局目录PGD中(通过VA_PGD定位),PA_CONTROL_PAGE的物理地址分别存到页表PA_PTE_0和PA_PTE_1中,
//使之可以通过PA和VA两种方式来访问PA_CONTROL_PAGE页
movl PTR(VA_PGD)(%ebp), %edi
movl PTR(VA_CONTROL_PAGE)(%ebp), %eax
andl $0xffc00000, %eax //取高10位,得到偏移量(单位为4字节)
shrl $20, %eax //取VA_CONTROL_PAGE对应的页地址,取高12位(本该右移22位,但考虑之后还有X4字节得到页表在pgd的地址,才右移20位)
addl %edi, %eax //与VA_PGD的页地址相加,这个应该是PA_PTE_0在全局页目录(VA_PGD)中对应的页目录项的地址
movl PTR(PA_PTE_0)(%ebp), %edx
orl $PAGE_ATTR, %edx //1.取PA_PTE_0对应的页地址,并加上末12位的页属性(0x63),然后存到上述eax对应的地址所代表的内存中
movl %edx, (%eax)
movl PTR(VA_PTE_0)(%ebp), %edi
movl PTR(VA_CONTROL_PAGE)(%ebp), %eax
andl $0x003ff000, %eax //取中间10位
shrl $10, %eax //本应右移12位,但考虑之后还有X4字节得到页在页表中的地址,才右移10位
addl %edi, %eax //与VA_PTE_0的页地址相加,这个应该是VA_CONTROL_PAGE在页表(VA_PTE_0)中对应的页表项的地址
movl PTR(PA_CONTROL_PAGE)(%ebp), %edx
orl $PAGE_ATTR, %edx
movl %edx, (%eax) //2.取PA_CONTROL_PAGE对应的页地址,并加上末12位的页属性(0x63),然后存到上述eax对应的地址所代表的内存中
/* identity map the control page at its physical address */
movl PTR(VA_PGD)(%ebp), %edi
movl PTR(PA_CONTROL_PAGE)(%ebp), %eax
andl $0xffc00000, %eax
shrl $20, %eax
addl %edi, %eax
movl PTR(PA_PTE_1)(%ebp), %edx
orl $PAGE_ATTR, %edx
movl %edx, (%eax) //3.
movl PTR(VA_PTE_1)(%ebp), %edi
movl PTR(PA_CONTROL_PAGE)(%ebp), %eax
andl $0x003ff000, %eax
shrl $10, %eax
addl %edi, %eax
movl PTR(PA_CONTROL_PAGE)(%ebp), %edx
orl $PAGE_ATTR, %edx
movl %edx, (%eax) //4.
#endif
relocate_new_kernel:
/* read the arguments and say goodbye to the stack *///马上就要切换页表了
//自栈中读取参数,并将它们存储到寄存器中,然后禁用中断
+-----------------------+
| ... . |
|-------------------------|esp+16
| cpu_has_pae |
|-------------------------|esp+12
| start_address |
|-------------------------|esp+8
| list_of_page |
|-------------------------|esp+4
| page_list |
+-----------------------+esp
movl 4(%esp), %ebx /* page_list */
movl 8(%esp), %ebp /* list of pages */
movl 12(%esp), %edx /* start address */
movl 16(%esp), %ecx /* cpu_has_pae */
/* zero out flags, and disable interrupts */
pushl $0
popfl
/* get physical address of control page now */
/* this is impossible after page table switch */
movl PTR(PA_CONTROL_PAGE)(%ebp), %edi
/* switch to new set of page tables *///切换页表,和旧内核byebye了
movl PTR(PA_PGD)(%ebp), %eax
movl %eax, %cr3
/* setup a new stack at the end of the physical control page *///在页的末端设置一个栈
lea 4096(%edi), %esp
/* jump to identity mapped page *///identity_mapped,relocate_kernel以及此时的栈应该在同一页内
movl %edi, %eax
addl $(identity_mapped - relocate_kernel), %eax
pushl %eax
ret
identity_mapped:
/* store the start address on the stack */
pushl %edx //执行完identity_mapped后,弹出此处压栈的edx(新内核入口函数)并执行,对应最后的ret
//设置 cr0 寄存器的适当位来禁用内存分页;将页目录基址寄存器 cr4 重设为 0。
/* Set cr0 to a known state:
* 31 0 == Paging disabled
* 18 0 == Alignment check disabled
* 16 0 == Write protect disabled
* 3 0 == No task switch
* 2 0 == Don't do FP software emulation.
* 0 1 == Proctected mode enabled
*/
movl %cr0, %eax
andl $~((1<<31)|(1<<18)|(1<<16)|(1<<3)|(1<<2)), %eax
orl $(1<<0), %eax
movl %eax, %cr0 //设置cr0寄存器的适当位来禁用内存分页。
/* clear cr4 if applicable */
testl %ecx, %ecx
jz 1f
/* Set cr4 to a known state:
* Setting everything to zero seems safe.
*/
movl %cr4, %eax
andl $0, %eax
movl %eax, %cr4 //将页目录基址寄存器cr4重设为 0。
jmp 1f
1:
/* Flush the TLB (needed?) *///清空快表(Translation Lookaside Buffers)
xorl %eax, %eax
movl %eax, %cr3
//#将所有内核映像页拷贝到最终目标页??????############
//按照网上一些资料,应该是将旧内核映像拷贝到其他内存中、覆盖旧内核,然后启动新内核。 这个看的还不白
/* Do the copies */
movl %ebx, %ecx //%ecx=%ebx=image->head
jmp 1f
0: /* top, read another word from the indirection page */
movl (%ebx), %ecx
addl $4, %ebx
1:
testl $0x1, %ecx /* is it a destination page */
jz 2f
movl %ecx, %edi
andl $0xfffff000, %edi
jmp 0b
2:
testl $0x2, %ecx /* is it an indirection page */
jz 2f
movl %ecx, %ebx
andl $0xfffff000, %ebx
jmp 0b
2:
testl $0x4, %ecx /* is it the done indicator */
jz 2f
jmp 3f
2:
testl $0x8, %ecx /* is it the source indicator */
jz 0b /* Ignore it otherwise */
movl %ecx, %esi /* For every source page do a copy */
andl $0xfffff000, %esi
movl $1024, %ecx
rep ; movsl //重复移动!!!!!!!
jmp 0b
//#############################################
3:
/* To be certain of avoiding problems with self-modifying code
* I need to execute a serializing instruction here.
* So I flush the TLB, it's handy, and not processor dependent.//再次清空 TLB
*/
xorl %eax, %eax
movl %eax, %cr3
/* set all of the registers to known values */
/* leave %esp alone *///将除了栈指针寄存器 esp(因为它指向容纳新内核起始地址的栈)以外的所有寄存器重设为 0。
xorl %eax, %eax
xorl %ebx, %ebx
xorl %ecx, %ecx
xorl %edx, %edx
xorl %esi, %esi
xorl %edi, %edi
xorl %ebp, %ebp
ret //自存根代码“返回”。自动将系统引导到新内核
|