分类: LINUX
2009-05-03 16:25:09
/*
* linux/arch/i386/kernel/head.S -- the 32-bit startup code.
*/
.text
/*
*......
*/
/*
* This is how much memory *in addition to the memory covered up to
* and including _end* we need mapped initially.
* We need:
* - one bit for each possible page, but only in low memory, which means
* 2^32/4096/8 = 128K worst case (4G/4G split.)
* - enough space to map all low memory, which means
* (2^32/4096) / 1024 pages (worst case, non PAE)
* (2^32/4096) / 512 + 4 pages (worst case for PAE)
* - a few pages for allocator use before the kernel pagetable has
* been set up
*
* Modulo rounding, each megabyte assigned here requires a kilobyte of
* memory, which is currently unreclaimed.
*
* This should be a multiple of a page.
*/
LOW_PAGES = 1<<(32-PAGE_SHIFT_asm) //LOW_PAGES=1MB,PAGE_SHIFT_asm = 12
#if PTRS_PER_PMD > 1
PAGE_TABLE_SIZE = (LOW_PAGES / PTRS_PER_PMD) + PTRS_PER_PGD
#else
PAGE_TABLE_SIZE = (LOW_PAGES / PTRS_PER_PGD)
#endif
BOOTBITMAP_SIZE = LOW_PAGES / 8 //BOOTITMAP_SIZE=128KB
ALLOCATOR_SLOP = 4
INIT_MAP_BEYOND_END = BOOTBITMAP_SIZE + (PAGE_TABLE_SIZE + ALLOCATOR_SLOP)*PAGE_SIZE_asm
/*
* 32-bit kernel entrypoint; only used by the boot CPU. On entry,
* %esi points to the real-mode code as a 32-bit pointer.
* CS and DS must be 4 GB flat segments, but we don't depend on
* any particular GDT layout, because we load our own as soon as we
* can.
*/
.section .text.head,"ax",@progbits
ENTRY(startup_32) //linux内核代码第一个字节的地址
/*
* Set segments to known values.
*/
cld
lgdt boot_gdt_descr - __PAGE_OFFSET //加载GDTR寄存器,从文件head.S后面的全局数据段可以看出,在初始化引导阶段,GDT表的第一项和第二项设为0,第三和第四项分别设置为0x00cf9a000000ffff 0x00cf92000000ffff.
movl $(__BOOT_DS),%eax //__BOOT_DS=3*8
movl %eax,%ds //
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
/*
* Clear BSS first so that there are no surprises...
* No need to cld as DF is already clear from cld above...
*/
xorl %eax,%eax //Clear eax
movl $__bss_start - __PAGE_OFFSET,%edi //__bss_start is the symbol of uninitialized segment
movl $__bss_stop - __PAGE_OFFSET,%ecx //__bss_end is the symbol of the end of the uninitialized segment
subl %edi,%ecx //Register holds the value of the length of the bss segment.
shrl $2,%ecx //the following two codes set all of the bss segment to be 0
rep ; stosl //设置未初始化段为0
/*
* Copy bootup parameters out of the way.
* Note: %esi still has the pointer to the real-mode data.
* With the kexec as boot loader, parameter segment might be loaded beyond
* kernel image and might not even be addressable by early boot page tables.
* (kexec on panic case). Hence copy out the parameters before initializing
* page tables.
*/
/*
*......与内存引导无关的代码
*/
1:
/*
* Initialize page tables. This creates a PDE(页目录项) and a set of page
* tables, which are located immediately beyond _end. The variable
* init_pg_tables_end is set up to point to the first "safe" location.
* Mappings are created both at virtual address 0 (identity mapping)
* and PAGE_OFFSET for up to _end+sizeof(page tables)+INIT_MAP_BEYOND_END.
*
* Warning: don't use %esi or the stack in this code. However, %esp
* can be used as a GPR if you really need it...
*/
page_pde_offset = (__PAGE_OFFSET >> 20); //
movl $(pg0 - __PAGE_OFFSET), %edi //临时页表在pg0变量处开始存放
movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
movl $0x007, %eax /* 0x007 = PRESENT+RW+USER */
10:
leal 0x007(%edi),%ecx /* Create PDE entry */ //
movl %ecx,(%edx) /* Store identity PDE entry */
//将第一个页表的第三项放入到swapper_pg_dir的第一项,设置页目录项所对应页表的最后三位,设置页表属性。
movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
//保存内核页表,这里是分页的第一个阶段,所以要实现在实
//模式下和保护模式能很容易地进行寻址
addl $4,%edx //这里肯定要将edx加4了,因为前面已经映射了一项了,这里就仅仅是来设置页全局目录的第一项而已。这一点是要注意的。
movl $1024, %ecx
11:
stosl //此处设置的是第一个页表内的值,对于pg0映射的是第一个4MB页框
addl $0x1000,%eax //设置页全局目录表,这里仅仅设置了第一个页全局目录项
loop 11b
/* End condition: we must map up to and including INIT_MAP_BEYOND_END */
/* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
cmpl %ebp,%eax
jb 10b
movl %edi,(init_pg_tables_end - __PAGE_OFFSET)
xorl %ebx,%ebx /* This is the boot CPU (BSP) */
jmp 3f
//这里是i386,所以就直接跳转到3处了,不需要执行SMP,这里针对的是单CPU
/*
*......
*
/*
* Enable paging
*/
3: movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. ,*/ //设置页全局目录
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */// Register cr0 takes the value of 0x80000000,启动分页机制。
ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip *///正规化eip指针,进入到页保护模式执行
1:
/* Set up the stack pointer */
lss stack_start,%esp //设置堆栈段
/*
* Initialize eflags. Some BIOS's leave bits like NT set. This would
* confuse the debugger if this code is traced.
* XXX - best to initialize before switching to protected mode.
*/
pushl $0
popfl
/*
* start system 32-bit setup. We need to re-do some of the things done
* in 16-bit mode for the "real" operations.
*/
call setup_idt
checkCPUtype:
......
is386: movl $2,%ecx # set MP 设置协处理器位
2: movl %cr0,%eax
andl $0x80000011,%eax # Save PG,PE,ET
orl %ecx,%eax
movl %eax,%cr0
call check_x87
lgdt early_gdt_descr //重新设置GDTR寄存器
lidt idt_descr
ljmp $(__KERNEL_CS),$1f
/*__KERNEL_CS=12*8,进入到内核空间,上面的代码都是作为引导用的,通过这个跳转,正式进入到内核代码段*/
1: movl $(__KERNEL_DS),%eax # reload all the segment registers
//进入内核段,重载所有的段寄存器
movl %eax,%ss # after changing gdt.
movl %eax,%fs # gets reset once there's real percpu
movl $(__USER_DS),%eax # DS/ES contains default USER segment
movl %eax,%ds
movl %eax,%es
xorl %eax,%eax # Clear GS and LDT
movl %eax,%gs
lldt %ax
cld # gcc2 wants the direction flag cleared at all times
pushl $0 # fake return address for unwinder
jmp start_kernel //跳转进入内核启动阶段
/*
* We depend on ET to be correct. This checks for 287/387.
*/
check_x87:
movb $0,X86_HARD_MATH
clts
fninit
fstsw %ax
cmpb $0,%al
je 1f
movl %cr0,%eax /* no coprocessor: have to set bits */
xorl $4,%eax /* set EM */
movl %eax,%cr0
ret
ALIGN
1: movb $1,X86_HARD_MATH
.byte 0xDB,0xE4 /* fsetpm for 287, ignored by 387 */
ret
/*
* setup_idt
*
* sets up a idt with 256 entries pointing to
* ignore_int, interrupt gates. It doesn't actually load
* idt - that can be done only after paging has been enabled
* and the kernel moved to PAGE_OFFSET. Interrupts
* are enabled elsewhere, when we can be relatively
* sure everything is ok.
*
* Warning: %esi is live across this function.
*/
/*
*......
*/
.section .text
/*
* Real beginning of normal "text" segment
*/
ENTRY(stext)
ENTRY(_stext)
/*
* BSS section
*/
.section ".bss.page_aligned","wa"
.align PAGE_SIZE_asm
ENTRY(swapper_pg_dir)
.fill 1024,4,0 //页全局目录表初始化为0
ENTRY(swapper_pg_pmd)
.fill 1024,4,0 //页中间表项初始化为0
ENTRY(empty_zero_page)
.fill 4096,1,0 //Initializing all of the page 0 at the Original Booting time!
/*
* This starts the data section.
*/
.data
ENTRY(stack_start)
.long init_thread_union+THREAD_SIZE
.long __BOOT_DS
ready: .byte 0
early_recursion_flag:
.long 0
int_msg:
.asciz "Unknown interrupt or fault at EIP %p %p %p\n"
fault_msg:
.ascii "Int %d: CR2 %p err %p EIP %p CS %p flags %p\n"
.asciz "Stack: %p %p %p %p %p %p %p %p\n"
#include "../xen/xen-head.S"
/*
* The IDT and GDT 'descriptors' are a strange 48-bit object
* only used by the lidt and lgdt instructions. They are not
* like usual segment descriptors - they consist of a 16-bit
* segment size, and 32-bit linear address value:
*/
.globl boot_gdt_descr
.globl idt_descr
ALIGN
# early boot GDT descriptor (must use 1:1 address mapping)
.word 0 # 32 bit align gdt_desc.address
boot_gdt_descr:
.word __BOOT_DS+7
.long boot_gdt - __PAGE_OFFSET
.word 0 # 32-bit align idt_desc.address
idt_descr:
.word IDT_ENTRIES*8-1 # idt contains 256 entries
.long idt_table
# boot GDT descriptor (later on used by CPU#0):
.word 0 # 32 bit align gdt_desc.address
ENTRY(early_gdt_descr)
.word GDT_ENTRIES*8-1 //GDT_ENTRIES=32
.long per_cpu__gdt_page /* Overwritten for secondary CPUs */
/*
* The boot_gdt must mirror the equivalent in setup.S and is
* used only for booting.
*/
.align L1_CACHE_BYTES
ENTRY(boot_gdt)
.fill GDT_ENTRY_BOOT_CS,8,0 //GDT_ENTRY_BOOT_CS=2
.quad 0x00cf9a000000ffff /* kernel 4GB code at 0x00000000 */
//第一、二,第七位到第十二共八个数字,每个数字是四位,来表示线性地址。
.quad 0x00cf92000000ffff /* kernel 4GB data at 0x00000000 */