标签 Assembly 下的文章

Kernel 'head.S'

After being decompressed, the kernel image starts with another ‘startup_32′ function included in $(linux-2.6.15.3_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.

/*
 * ! $(linux2.6.3.15_dir)/arch/i386/kernel/head.S
 */
ENTRY(startup_32)

 /*
  * ! We still use liner address, since
  * ! %ds = %es = %fs = %gs = __BOOT_DS
  * ! we use the third segment which base
  * ! address starts from 0×00000000
  */
 cld
 lgdt boot_gdt_descr – __PAGE_OFFSET
 movl $(__BOOT_DS),%eax
 movl %eax,%ds
 movl %eax,%es
 movl %eax,%fs
 movl %eax,%gs

 /*
  * ! Clear the kernel bss
  */
 xorl %eax,%eax
 movl $__bss_start – __PAGE_OFFSET,%edi
 movl $__bss_stop – __PAGE_OFFSET,%ecx
 subl %edi,%ecx
 shrl $2,%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 */
10:
 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 */
 addl $4,%edx
 movl $1024, %ecx
11:
 stosl
 addl $0×1000,%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 +0×007 is the attribute bits */
 leal (INIT_MAP_BEYOND_END+0×007)(%edi),%ebp
 cmpl %ebp,%eax
 jb 10b
 movl %edi,(init_pg_tables_end – __PAGE_OFFSET)

 /*
  * ! here just the boot CPU go this way
  */
#ifdef CONFIG_SMP
 xorl %ebx,%ebx    /* This is the boot CPU (BSP) */
 jmp 3f

The kernel page tables have been loaded and we can enable the paging now!

 /*
  * Enable paging
  */
 movl $swapper_pg_dir-__PAGE_OFFSET,%eax
 
 /*
  * ! load the table physical address into the %cr3
  */
 movl %eax,%cr3  /* set the page table pointer.. */
 movl %cr0,%eax
 orl $0×80000000,%eax
 
 /*
  * ! 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 */
1:
 /* Set up the stack pointer */
 lss stack_start,%esp

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’
  */
 call setup_idt

 ….
 ….

setup_idt:
 lea ignore_int,%edx
 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 $(linux2.6.3.15_dir)/arch/i386/kernel/traps.c
  */
 lea idt_table,%edi
 mov $256,%ecx
rp_sidt:
 movl %eax,(%edi)
 movl %edx,4(%edi)
 addl $8,%edi
 dec %ecx
 jne rp_sidt
 ret

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
  */
 lgdt cpu_gdt_descr
 lidt idt_descr
 ljmp $(__KERNEL_CS),$1f
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
 movl %eax,%ds
 movl %eax,%es

 xorl %eax,%eax   # Clear FS/GS and LDT
 movl %eax,%fs
 movl %eax,%gs
 lldt %ax
 cld   # gcc2 wants the direction flag cleared at all times

 …
 …

 /*
  * ! The boot CPU will jump to execute
  * ! $(linux2.6.3.15_dir)/init/main.c:start_kernel()
  * ! And the start_kernel() should never return :)
  */
 call start_kernel
L6:
 jmp L6   # main should never return here, but
    # just in case, we know what happens.

Compressed 'head.S'

Why do we do this? Don’t ask me.. Incomprehensible are the ways of bootloaders.
                             — comments in arch/i386/boot/compressed/misc.c

There are two ‘head.S’ in linux source package. One is in $(Linux-2.6.15.3_dir/arch/i386/boot/compressed and the other one is in $(Linux-2.6.15.3_dir/arch/i386/kernel. The first one will be analyzed in this artical. Before we go ahead, let’s show a news of linux, that is ‘Army leans toward Linux for FCS(Future Combat System)’.

The first ‘head.S’ is also called ‘compressed head’, which used to decompress the kernel image. Different from those code before, we are now in 32-bit protected mode with paging disabled. The ‘compressed head’ starts from ‘startup_32′.

.text /* ! here just ‘.text’, without ‘.code16′ assembly directive */
.globl startup_32
 
startup_32:
 /*
  * ! clear direction flag
  * ! and clear interrupt flag
  */
 cld
 cli

 /*
  * ! all other segment registers are
  * ! reloaded after protected mode enabled
  * ! __BOOT_DS = 0×18
  */
 movl $(__BOOT_DS),%eax
 movl %eax, %ds
 movl %eax, %es
 movl %eax, %fs
 movl %eax, %gs

 /*
  * ! lss – load full pointer from memory
  * !       to register
  * ! and here ‘ss:esp = stack_start’
  */
 lss stack_start,%esp

 /*
  * ! EAX = 0;
  * ! do {
  * !     DS:[0] = ++EAX;
  * ! } while (DS:[0x100000] == EAX);
  */
 xorl %eax, %eax
1: incl %eax  # check that A20 really IS enabled
 movl %eax, 0×000000 # loop forever if it isn’t
 cmpl %eax, 0×100000
 je 1b

After reload the segment registers, the ‘compressed head’ clears the ‘eflags’ register and fills the kernel bss(the area of uninitialized data of the kernel identified by the _edata and _end symbols) with zeros. Then the decompressed process begins.

 /*
  * ! %esi has been loaded in ‘setup.S’ with ‘INITSET << 4′
  * ! ‘subl $16,%esp’ used to store the first arg, that is
  * ! struct moveparams {
  * !     uch *low_buffer_start;
  * !     int lcount;
  * !     uch *high_buffer_start;
  * !     int hcount;
  * ! } mv;
  * ! the second arg is the %esi which indicates the position
  * ! of the real-mode data
  */
 subl $16,%esp # place for structure on the stack
 movl %esp,%eax
 pushl %esi # real mode pointer as second arg
 pushl %eax # address of structure as first arg

 /*
  * ! if (!decompress_kernel(&mv, esi)) {         // return value in AX
  * !    restore esi from stack;
  * !    ebx = 0;
  * !    goto __BOOT_CS: $__PHYSICAL_START;
  * !    // see linux/arch/i386/kernel/head.S:startup_32
  * ! }
  * ! ‘decompress_kernel’ is coded in
  * ! $(linux-2.6.15.3_dir)/arch/i386/boot/compressed/misc.c
  *
/
 call decompress_kernel
 orl  %eax,%eax
 jnz  3f
 popl %esi # discard address
 popl %esi # real mode pointer
 xorl %ebx,%ebx
 ljmp $(__BOOT_CS), $__PHYSICAL_START

3:
 /*
  * ! move move_rountine_start..move_routine_end to 0×1000
  * ! both the two functions are defined in the tail of
  * ! this file
  */
 movl $move_routine_start,%esi
 movl $0×1000,%edi
 movl $move_routine_end,%ecx
 subl %esi,%ecx
 addl $3,%ecx
 shrl $2,%ecx
 cld
 rep
 movsl

 /*
  * ! Do preparation for ‘move_routine_start’:
  * ! set the parameters
  * ! ebx = real mode pointer
  * ! esi = mv.low_buffer_start
  * ! ecx = mv.lcount
  * ! edx = mv.high_buffer_start
  * ! eax = mv.hcount
  * ! edi = $__PHYSICAL_START
  */
 popl %esi # discard the address
 popl %ebx # real mode pointer
 popl %esi # low_buffer_start
 popl %ecx # lcount
 popl %edx # high_buffer_start
 popl %eax # hcount
 movl $__PHYSICAL_START,%edi
 cli  # make sure we don’t get interrupted

 /*
  * ! jump to physical address: __BOOT_CS:0×1000
  * ! where the move_routine_start function stays
  */
 ljmp $(__BOOT_CS), $0×1000 # and jump to the move routine

 /*
  * ! the control has been transfered to ‘move_routine_start’
  */
move_routine_start:
 movl %ecx,%ebp
 shrl $2,%ecx
 rep
 movsl
 movl %ebp,%ecx
 andl $3,%ecx
 rep
 movsb
 movl %edx,%esi
 movl %eax,%ecx # NOTE: rep movsb won’t move if %ecx == 0
 addl $3,%ecx
 shrl $2,%ecx
 rep
 movsl
 movl %ebx,%esi # Restore setup pointer
 xorl %ebx,%ebx
 ljmp $(__BOOT_CS), $__PHYSICAL_START
move_routine_end:

In ‘move_routine_start’, we perform the operations as follows:
(1) move mv.low_buffer_start to $__PHYSICAL_START, (mv.lcount >> 2) words;
(2) move/append (mv.lcount & 3) bytes;
(3) move/append mv.high_buffer_start, ((mv.hcount + 3) >> 2) words.

After move the decompressed kernel image to its right place, the control will be transfered to physical address:’$(__BOOT_CS):$__PHYSICAL_START’, where the second ‘head.S’ stays.

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats