After being decompressed, the kernel image starts with another ‘startup_32′ function included in $(linux-188.8.131.52_dir/arch/i386/kernel/head.S’. This ‘head.S’ is the second one in linux source package, which is also called ‘kernel head’. And it is exactly what we want to describe in this artical.
The kernel head continues to perform higher initialization operations for the first linux process(process 0). It sets up an execution environment for the kernel main routine just like what the operating system does before an application begins to start. There are two entries for CPUs in this ‘head.S’ and we only talk about the execution routine of the boot CPU.
* ! $(linux184.108.40.206_dir)/arch/i386/kernel/head.S
* ! We still use liner address, since
* ! %ds = %es = %fs = %gs = __BOOT_DS
* ! we use the third segment which base
* ! address starts from 0×00000000
lgdt boot_gdt_descr – __PAGE_OFFSET
* ! Clear the kernel bss
movl $__bss_start – __PAGE_OFFSET,%edi
movl $__bss_stop – __PAGE_OFFSET,%ecx
rep ; stosl
After copying the bootup parameters, it prepares to enable the paging. Before the paging enabled, some data structure should be loaded first following the ‘Intel Manual Vol3′.
* ! Initialize the provisional kernel page tables
* ! which are stored starting from pg0, right after
* ! the end of the kernel’s uninitialized data segments(bss).
* ! and the provisional page global directory is
* ! contained in the swapper_pg_dir variable.
* ! page_pde_offset = 0x0c00
page_pde_offset = (__PAGE_OFFSET >> 20);
* ! this line indicates the table starts from ‘pg0′
movl $(pg0 – __PAGE_OFFSET), %edi
* ! this line told us ‘swapper_pg_dir’ is the
* ! page directory start point
movl $(swapper_pg_dir – __PAGE_OFFSET), %edx
* ! There were 1024 entries in ‘swapper_pg_dir’
* ! since the code below:
* ! ENTRY(swapper_pg_dir)
* ! .fill 1024,4,0
* ! The first mapping:
* ! both entry 0 and entry 0×300 (page_pde_offset/4) –> pg0
* ! that is (0×00000000~0x007fffff) —> pg0
* ! The second mapping:
* ! both entry 1 and entry 0×301 (page_pde_offset/4+1) –> pg1 (the page following pg0)
* ! that is (0xC0000000~0xC07fffff) —> pg1
* ! The objective of this first phase of paging is to
* ! allow these 8 MB of RAM to be easily addressed
* ! both in real mode and protected mode.
movl $0×007, %eax /* 0×007 = PRESENT+RW+USER */
leal 0×007(%edi),%ecx /* Create PDE entry */
movl %ecx,(%edx) /* Store identity PDE entry */
movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
movl $1024, %ecx
/* End condition: we must map up to and including INIT_MAP_BEYOND_END */
/* bytes beyond the end of our own page tables; the +0×007 is the attribute bits */
movl %edi,(init_pg_tables_end – __PAGE_OFFSET)
* ! here just the boot CPU go this way
xorl %ebx,%ebx /* This is the boot CPU (BSP) */
The kernel page tables have been loaded and we can enable the paging now!
* Enable paging
* ! load the table physical address into the %cr3
movl %eax,%cr3 /* set the page table pointer.. */
* ! Enable the paging
movl %eax,%cr0 /* ..and set paging (PG) bit */
* ! A relative jump after the paging enabled
ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */
/* Set up the stack pointer */
There is a relative jump instruction – ‘ljmp $(__BOOT_CS), $1f’. Maybe you wonder what the ‘$1f’ means. ’1′ is a local symbol. To define a local symbol, write a label of the form ‘N:’ (where N represents any digit). To refer to the most recent previous definition of that symbol write ‘Nb’, using the same digit as when you defined the label. To refer to the next definition of a local label, write ‘Nf’. The ‘b’ stands for "backwards" and the ‘f’ stands for "forwards".
Now we are in 32-bit protected mode with paging enable. so we still need to re-do something done in 16-bit mode for ‘real-mode’ operations.
* ! Setup the interrupt descriptor table
* ! All the 256 entries are pointing to
* ! the default interrupt "handler" — ‘ignore_int’
movl $(__KERNEL_CS << 16),%eax
movw %dx,%ax /* selector = 0×0010 = cs */
movw $0x8E00,%dx /* interrupt gate – dpl=0, present */
* ! idt_table varible is defined
* ! in $(linux220.127.116.11_dir)/arch/i386/kernel/traps.c
After checking the type of CPU, the kernel head prepare to call the kernel main function ‘start_kernel’.
* ! use new descriptor table in safe place
* ! then reload segment registers after lgdt
1: movl $(__KERNEL_DS),%eax # reload all the segment registers
movl %eax,%ss # after changing gdt.
movl $(__USER_DS),%eax # DS/ES contains default USER segment
xorl %eax,%eax # Clear FS/GS and LDT
cld # gcc2 wants the direction flag cleared at all times
* ! The boot CPU will jump to execute
* ! $(linux18.104.22.168_dir)/init/main.c:start_kernel()
* ! And the start_kernel() should never return
jmp L6 # main should never return here, but
# just in case, we know what happens.
© 2006, bigwhite. 版权所有.