内核启动之启动内核——startup_32

        这段代码在arch/x86/boot/kernel/header_32.S中,它是在内核被解压缩到0x100000处之后跳转到这个地址开始执行的,前面的操作算是为这一步做铺垫吧,现在到了真正的内核,为了能够让自己把内核代码真的弄懂,不再采用前面那种比较比较笼统的方式了,而是按照内核源码分析的那种方式将这个代码复制下来,然后逐行的分析,这样就不至于再给自己偷懒的机会了,嗯,以后就这样做,争取能够把不懂的内容搞懂。 

        在这个时候,我们已经进入了保护模式(也就是寻址方式发生了改变),但是没有启用分页,所以得到的线性地址就是物理地址,之前的bootloader的内容已经对我们没有任何意义了,唯一剩余的就是它留下来一个关于一些参数结构体boot_params,这个结构体的地址保存在寄存器esi中,现在重新开始内核的执行。

代码如下:

.text

/* 省略头文件 */

/* Physical address */
#define pa(X) ((X) - __PAGE_OFFSET)      //逻辑地址转换成物理地址,因为这时候已经开启了保护模式,所以内核对应着高端的1G内存,并且和物理内存直接映射


/*
 * References to members of the new_cpu_data structure.
 */


#define X86 new_cpu_data+CPUINFO_x86
#define X86_VENDOR new_cpu_data+CPUINFO_x86_vendor
#define X86_MODEL new_cpu_data+CPUINFO_x86_model
#define X86_MASK new_cpu_data+CPUINFO_x86_mask
#define X86_HARD_MATH new_cpu_data+CPUINFO_hard_math
#define X86_CPUID new_cpu_data+CPUINFO_cpuid_level
#define X86_CAPABILITY new_cpu_data+CPUINFO_x86_capability
#define X86_VENDOR_ID new_cpu_data+CPUINFO_x86_vendor_id


/*
 * This is how much memory in addition to the memory covered up to
 * and including _end we need mapped initially.
 * We need:
 *     (KERNEL_IMAGE_SIZE/4096) / 1024 pages (worst case, non PAE)
 *     (KERNEL_IMAGE_SIZE/4096) / 512 + 4 pages (worst case for PAE)
 *
 * Modulo rounding, each megabyte assigned here requires a kilobyte of
 * memory, which is currently unreclaimed.
 *
 * This should be a multiple of a page.
 *
 * KERNEL_IMAGE_SIZE should be greater than pa(_end)
 * and small than max_low_pfn, otherwise will waste some page table entries
 */


#if PTRS_PER_PMD > 1
#define PAGE_TABLE_SIZE(pages) (((pages) / PTRS_PER_PMD) + PTRS_PER_PGD)
#else
#define PAGE_TABLE_SIZE(pages) ((pages) / PTRS_PER_PGD)    //pages页需要的页表项
#endif


/* Number of possible pages in the lowmem region */
LOWMEM_PAGES = (((1<<32) - __PAGE_OFFSET) >> PAGE_SHIFT)         //内核能够映射的物理内存的大小,低端内存

/* Enough space to fit pagetables for the low memory linear map */
MAPPING_BEYOND_END = PAGE_TABLE_SIZE(LOWMEM_PAGES) << PAGE_SHIFT     //低端内存所占用的页表项的大小


/*
 * Worst-case size of the kernel mapping we need to make:
 * a relocatable kernel can live anywhere in lowmem, so we need to be able
 * to map all of lowmem.
 */
KERNEL_PAGES = LOWMEM_PAGES                            //内核能够映射的内存大小


INIT_MAP_SIZE = PAGE_TABLE_SIZE(KERNEL_PAGES) * PAGE_SIZE           //同MAPPING_BEYOND_END
RESERVE_BRK(pagetables, INIT_MAP_SIZE)


/*
 * 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.
 */
__HEAD
ENTRY(startup_32)                    //0x100000,从这里开始执行内核的代码
movl pa(stack_start),%ecx            //这里设置堆栈

/* test KEEP_SEGMENTS flag to see if the bootloader is asking
us to not reload segments */
testb $(1<<6), BP_loadflags(%esi)          //判断是否需要重新设置各个段寄存器
jnz 2f


/*
 * Set segments to known values.
 */
lgdt pa(boot_gdt_descr)                     //设置临时的全局页描述符表,后面还需要进一步初始化
movl $(__BOOT_DS),%eax                      //初始化各个段寄存器
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
movl %eax,%ss
2:
leal -__PAGE_OFFSET(%ecx),%esp             //设置堆栈的栈顶指针,这个堆栈就是init_task的堆栈,这里相当于手工初始化了一个进程

/*
 * Clear BSS first so that there are no surprises...
 */                                                 //初始化bss段,清0
cld
xorl %eax,%eax
movl $pa(__bss_start),%edi                          //bss段的起始位置
movl $pa(__bss_stop),%ecx                           
subl %edi,%ecx                                      //计算bss段的大小
shrl $2,%ecx                                        //以4字节为单位对bss段进行清0
rep ; stosl
/*
 * 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.
 */
movl $pa(boot_params),%edi            //拷贝boot_params的内容
movl $(PARAM_SIZE/4),%ecx
cld
rep
movsl
movl pa(boot_params) + NEW_CL_POINTER,%esi         //这里保存的是boot_params.hdr.cmd_line_ptr
andl %esi,%esi
jz 1f # No command line
movl $pa(boot_command_line),%edi
movl $(COMMAND_LINE_SIZE/4),%ecx
rep
movsl
1:


#ifdef CONFIG_OLPC
/* save OFW's pgdir table for later use when calling into OFW */
movl %cr3, %eax
movl %eax, pa(olpc_ofw_pgd)
#endif


/*
 * Initialize page tables.  This creates a PDE and a set of page
 * tables, which are located immediately beyond __brk_base.  The variable
 * _brk_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.
 */
#ifdef CONFIG_X86_PAE             //忽略使用PAE,假设没有开始PAE模式


/*
* In PAE mode initial_page_table is statically defined to contain
* enough entries to cover the VMSPLIT option (that is the top 1, 2 or 3
* entries). The identity mapping is handled by pointing two PGD entries
* to the first kernel PMD.
*
* Note the upper half of each PMD or PTE are always zero at this stage.
*/


#define KPMDS (((-__PAGE_OFFSET) >> 30) & 3) /* Number of kernel PMDs */


xorl %ebx,%ebx/* %ebx is kept at zero */


movl $pa(__brk_base), %edi
movl $pa(initial_pg_pmd), %edx
movl $PTE_IDENT_ATTR, %eax
10:
leal PDE_IDENT_ATTR(%edi),%ecx/* Create PMD entry */
movl %ecx,(%edx)/* Store PMD entry */
/* Upper half already zero */
addl $8,%edx
movl $512,%ecx
11:
stosl
xchgl %eax,%ebx
stosl
xchgl %eax,%ebx
addl $0x1000,%eax
loop 11b


/*
* End condition: we must map up to the end + MAPPING_BEYOND_END.
*/
movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
cmpl %ebp,%eax
jb 10b
1:
addl $__PAGE_OFFSET, %edi
movl %edi, pa(_brk_end)
shrl $12, %eax
movl %eax, pa(max_pfn_mapped)


/* Do early initialization of the fixmap area */
movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax
movl %eax,pa(initial_pg_pmd+0x1000*KPMDS-8)
#else /* Not PAE */

page_pde_offset = (__PAGE_OFFSET >> 20);          //0x0C00,这里是内核所使用的第一个项页目录项在全局页目录表中的索引

movl $pa(__brk_base), %edi            //找到_brk_base,从这个地址开始存放页表,由于对于4G的地址空间,只需要一个全局页目录表就足够了,于是在这里定义了这个全局页目录表,也就是initial_page_table,这里我们需要设置它每一项所对应的页表,__brk_base是从64K的地址开始的。
movl $pa(initial_page_table), %edx         //全局页目录表的首地址
movl $PTE_IDENT_ATTR, %eax                 //内核使用的页表项的属性
10:
leal PDE_IDENT_ATTR(%edi),%ecx/* Create PDE entry */         //内核使用的全局页目录项的属性,这里因为dei保存的是第一个页表的首地址(低12位对齐),所以使用加法相当于这个地址与属性域进行或操作,这也就是第一个页目录项的值(高20位的地址域正好对应到__brk_base,属性为这里设置的)
movl %ecx,(%edx)/* Store identity PDE entry */               //将这里的全局页目录项的第一项(索引为0的那一项,是用户地址空间的页目录项)设置为刚才构造的值,但是这一项不会被使用,因为在init_task的地址空间中并不访问用户地址空间。这里的ecx的内容是0x00010067,也就是页目录项的第一项,所以当查找内地址空间0xC0000000的时候,它会找到全局页目录表的0x0C00项,得到的页目录项0x00010067,所以它映射的地址是0x00010000,也就是64K的地址,这里是一个页表,然后再通过查找这个页表取得地址。
movl %ecx,page_pde_offset(%edx)/* Store kernel PDE entry */       //这里将全局页目录项的第0x0C00项也设置为刚才构造的值,这里可以看出,全局页目录项的0项和0x0C00项的内容是一样的。
addl $4,%edx
movl $1024, %ecx
11:
stosl                    //这里循环1024次,为每一个页表项进行初始化。初始化的内容是由eax提供的,初始化的时候eax的内容是0x00000003,然后随着每次的循环,eax加上0x1000,也就是指向下一个页面。这里初始化了第一个页表。初始化的第一个页表项是0x00000003,第二个页表项是0x00001003,第三个页表项是0x00002003,依次增加,这样上面的例子0xC0000000(内核地址空间的地址0)取出中间的10位(也就是0),得到的是页表的索引,所以得到的页表项就是0x00000003,因此它映射的物理地址就是0x0的位置,所以这里通过这个全局页目录表之后内核地址空间爱你和物理地址仍然是一一对应的(也就相当于物理地址 = 内核地址 - 0xC0000000,只不过得到的方式不一样吧了)。
addl $0x1000,%eax
loop 11b
/*
* End condition: we must map up to the end + MAPPING_BEYOND_END.
*/
movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp    
cmpl %ebp,%eax             //比较eax中的当前内容是否已经超出了内核能够映射的物理地址,如果没有那就需要继续映射
jb 10b                     //直到内核的地址空间全部进行映射
addl $__PAGE_OFFSET, %edi      
movl %edi, pa(_brk_end)           //将映射结束之后的最后一个页表的结束地址的线性地址保存在__brk_end中
shrl $12, %eax
movl %eax, pa(max_pfn_mapped)       //保存已经映射的页面数到max_pxn_mapped中


/* Do early initialization of the fixmap area */
movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax             //这里单独进行一个全局页目录项的映射,这一项是全局页目录表的最后一项,它映射的页表是initial_pg_fixmap,是一个空的页表,但是它的地址紧跟着这个全局页目录表。
movl %eax,pa(initial_page_table+0xffc)
#endif


#ifdef CONFIG_PARAVIRT                            //忽略。。。
/* This is can only trip for a broken bootloader... */
cmpw $0x207, pa(boot_params + BP_version)
jb default_entry

/* Paravirt-compatible boot parameters.  Look to see what architecture
we're booting under. */
movl pa(boot_params + BP_hardware_subarch), %eax
cmpl $num_subarch_entries, %eax
jae bad_subarch

movl pa(subarch_entries)(,%eax,4), %eax
subl $__PAGE_OFFSET, %eax
jmp *%eax


bad_subarch:
WEAK(lguest_entry)
WEAK(xen_entry)
/* Unknown implementation; there's really
  nothing we can do at this point. */
ud2a


__INITDATA

subarch_entries:
.long default_entry/* normal x86/PC */
.long lguest_entry/* lguest hypervisor */
.long xen_entry/* Xen hypervisor */
.long default_entry/* Moorestown MID */
num_subarch_entries = (. - subarch_entries) / 4
.previous
#else
jmp default_entry
#endif /* CONFIG_PARAVIRT */

/*
 * Non-boot CPU entry point; entered from trampoline.S
 * We can't lgdt here, because lgdt itself uses a data segment, but
 * we know the trampoline has already loaded the boot_gdt for us.
 *
 * If cpu hotplug is not supported then this code can go in init section
 * which will be freed later
 */

__CPUINIT

#ifdef CONFIG_SMP                 //SMP处理,忽略。。。
ENTRY(startup_32_smp)
cld
movl $(__BOOT_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
movl pa(stack_start),%ecx
movl %eax,%ss
leal -__PAGE_OFFSET(%ecx),%esp
#endif /* CONFIG_SMP */
default_entry:

/*
 * New page tables may be in 4Mbyte page mode and may
 * be using the global pages. 
 *
 * NOTE! If we are on a 486 we may have no cr4 at all!
 * So we do not try to touch it unless we really have
 * some bits in it to set.  This won't work if the BSP
 * implements cr4 but this AP does not -- very unlikely
 * but be warned!  The same applies to the pse feature
 * if not equally supported. --macro
 *
 * NOTE! We have to correct for the fact that we're
 * not yet offset PAGE_OFFSET..
 */
#define cr4_bits pa(mmu_cr4_features)
movl cr4_bits,%edx                    //判断是否启用4M的页面,这里不使用,直接跳转到6处执行。
andl %edx,%edx
jz 6f
movl %cr4,%eax# Turn on paging options (PSE,PAE,..)
orl %edx,%eax
movl %eax,%cr4

testb $X86_CR4_PAE, %al# check if PAE is enabled
jz 6f

/* Check if extended functions are implemented */
movl $0x80000000, %eax
cpuid
/* Value must be in the range 0x80000001 to 0x8000ffff */
subl $0x80000001, %eax
cmpl $(0x8000ffff-0x80000001), %eax
ja 6f

/* Clear bogus XD_DISABLE bits */
call verify_cpu

mov $0x80000001, %eax
cpuid
/* Execute Disable bit supported? */
btl $(X86_FEATURE_NX & 31), %edx
jnc 6f

/* Setup EFER (Extended Feature Enable Register) */
movl $MSR_EFER, %ecx
rdmsr

btsl $_EFER_NX, %eax
/* Make changes effective */
wrmsr

6:

/*
 * Enable paging
 */
movl $pa(initial_page_table), %eax
movl %eax,%cr3/* set the page table pointer.. */          //将全局页目录表的首地址装入到CR3寄存器。
movl %cr0,%eax
orl  $X86_CR0_PG,%eax
movl %eax,%cr0/* ..and set paging (PG) bit */             //开启分页机制!
ljmp $__BOOT_CS,$1f/* Clear prefetch and normalize %eip */        //跳转,清除预取的指令
1:
/* Shift the stack pointer to a virtual address */
addl $__PAGE_OFFSET, %esp                   //这里将esp设置为虚拟地址,主要是因为现在已经开启了分页机制,所以得到的线性地址必须经过分页单元才能得到物理地址,esp本来的内容是init_task进程的堆栈,它是一个虚拟地址,经过pa操作之后就得到了它的物理地址,也就是之前esp的内容,因为在分页机制开启之前,线性地址就是物理地址,现在得到线性地址之后需要进行分页单元的处理之后才能够得到物理地址,所以再次被映射到物理地址相同的地址。P.S.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             //初始化标志寄存器(EFLAGS)
popfl


#ifdef CONFIG_SMP
cmpb $0, ready
jnz checkCPUtype
#endif /* CONFIG_SMP */

/*
 * 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            //调用setup_idt进行中断描述符表的再次初始化

checkCPUtype:            //检查并测试一些CPU参数并保存

movl $-1,X86_CPUID#  -1 for no CPUID initially


/* check if it is 486 or 386. */
/*
 * XXX - this does a lot of unnecessary setup.  Alignment checks don't
 * apply at our cpl of 0 and the stack ought to be aligned already, and
 * we don't need to preserve eflags.
 */


movb $3,X86 # at least 386
pushfl # push EFLAGS
popl %eax # get EFLAGS
movl %eax,%ecx# save original EFLAGS
xorl $0x240000,%eax# flip AC and ID bits in EFLAGS
pushl %eax # copy to EFLAGS
popfl # set EFLAGS
pushfl # get new EFLAGS
popl %eax # put it in eax
xorl %ecx,%eax# change in flags
pushl %ecx # restore original EFLAGS
popfl
testl $0x40000,%eax# check if AC bit changed
je is386


movb $4,X86 # at least 486
testl $0x200000,%eax# check if ID bit changed
je is486


/* get vendor info */
xorl %eax,%eax# call CPUID with 0 -> return vendor ID
cpuid
movl %eax,X86_CPUID# save CPUID level
movl %ebx,X86_VENDOR_ID# lo 4 chars
movl %edx,X86_VENDOR_ID+4# next 4 chars
movl %ecx,X86_VENDOR_ID+8# last 4 chars


orl %eax,%eax # do we have processor info as well?
je is486


movl $1,%eax # Use the CPUID instruction to get CPU type
cpuid
movb %al,%cl # save reg for future use
andb $0x0f,%ah# mask processor family
movb %ah,X86
andb $0xf0,%al# mask model
shrb $4,%al
movb %al,X86_MODEL
andb $0x0f,%cl# mask mask revision
movb %cl,X86_MASK
movl %edx,X86_CAPABILITY


is486: movl $0x50022,%ecx# set AM, WP, NE and MP
jmp 2f


is386: movl $2,%ecx# set MP
2: movl %cr0,%eax
andl $0x80000011,%eax# Save PG,PE,ET
orl %ecx,%eax
movl %eax,%cr0               //设置CR0寄存器的一些标志位


call check_x87               //检查协处理器
lgdt early_gdt_descr         //装载全局描述符表,这里面仅仅保存了四项
lidt idt_descr               //装载中断描述符表,这里面简单设置了所有的256项
ljmp $(__KERNEL_CS),$1f      //再次跳转
1: movl $(__KERNEL_DS),%eax# reload all the segment registers
movl %eax,%ss # after changing gdt.
         //设置段寄存器,SS置为内核代码段(可执行)寄存器的内容
movl $(__USER_DS),%eax# DS/ES contains default USER segment
movl %eax,%ds
movl %eax,%es
         //ds和es设置为用户数据段的内容
movl $(__KERNEL_PERCPU), %eax
movl %eax,%fs # set this cpu's percpu

#ifdef CONFIG_CC_STACKPROTECTOR         //栈保护,忽略。。。
/*
* The linker can't handle this by relocation.  Manually set
* base address in stack canary segment descriptor.
*/
cmpb $0,ready
jne 1f
movl $gdt_page,%eax
movl $stack_canary,%ecx
movw %cx, 8 * GDT_ENTRY_STACK_CANARY + 2(%eax)
shrl $16, %ecx
movb %cl, 8 * GDT_ENTRY_STACK_CANARY + 4(%eax)
movb %ch, 8 * GDT_ENTRY_STACK_CANARY + 7(%eax)
1:
#endif
movl $(__KERNEL_STACK_CANARY),%eax
movl %eax,%gs
        //设置gs寄存器

xorl %eax,%eax# Clear LDT              //这里清除LDTR,因为我们不使用LDT
lldt %ax

cld # gcc2 wants the direction flag cleared at all times
pushl $0 # fake return address for unwinder       //如果返回,那么就会回到地址0处,出现错误。。。
movb $1, ready
jmp *(initial_code)            //这时候可以跳转了,并且一去不复返。。。


/*
 * 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.
 */
setup_idt:
lea ignore_int,%edx          
movl $(__KERNEL_CS << 16),%eax
movw %dx,%ax /* selector = 0x0010 = cs */
movw $0x8E00,%dx/* interrupt gate - dpl=0, present */
 //设置了每一项的中断描述符,因为中断描述符是64为,所以需要使用两个寄存器保存其内容,其中eax保存低32为内容,edx保存高32位内容,这里我们假设ignore_int的地址是XXXXYYYY,所以eax的内容就是00020YYYY , edx的内容就是XXXX8E00 , 因为它的标识字段是0xE,所以这是一个中断门,DPL为0,当发生中断的时候,就会跳转到这里取出这个中断描述符,然后根据选择子字段(0x0020)和偏移量(ignore_int的地址)就找到了ignore_int的地址,从这里开始执行中断处理程序,当然,这只是一个临时的中断处理程序,之后还需要进一步完善。

lea idt_table,%edi 
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx                 //设置全局的256项中断描述符,并且内容全部相同
jne rp_sidt


.macro set_early_handler handler,trapno              //这里定义了一个宏定义,它的作用是根据handler(中断处理程序)和trapno(中断号)两个参数在中断描述符表中设置对应的那一项
lea \handler,%edx
movl $(__KERNEL_CS << 16),%eax
movw %dx,%ax
movw $0x8E00,%dx/* interrupt gate - dpl=0, present */
lea idt_table,%edi
movl %eax,8*\trapno(%edi)
movl %edx,8*\trapno+4(%edi)
.endm


set_early_handler handler=early_divide_err,trapno=0           //除法错误(除以0操作)
set_early_handler handler=early_illegal_opcode,trapno=6       //无效操作码
set_early_handler handler=early_protection_fault,trapno=13    //通用保护(越权访问)
set_early_handler handler=early_page_fault,trapno=14          //缺页中断
//虽然前面已经设置了所有的中断描述符表,但是那只是一个十分简陋的中断处理程序,由于以上四个中断(异常)相对比较重要,所以这里再一次强化了他们的处理程序,但是这仍不是最终的处理程序。

ret                       //返回


early_divide_err:
xor %edx,%edx
pushl $0 /* fake errcode */
jmp early_fault


early_illegal_opcode:
movl $6,%edx
pushl $0 /* fake errcode */
jmp early_fault


early_protection_fault:
movl $13,%edx
jmp early_fault


early_page_fault:
movl $14,%edx
jmp early_fault


early_fault:             //以上四个处理程序公用的函数
cld
#ifdef CONFIG_PRINTK
pusha
movl $(__KERNEL_DS),%eax
movl %eax,%ds
movl %eax,%es
cmpl $2,early_recursion_flag              //最多发生两次这样的错误
je hlt_loop
incl early_recursion_flag
movl %cr2,%eax
pushl %eax
pushl %edx /* trapno */
pushl $fault_msg
call printk
#endif
call dump_stack
hlt_loop:
hlt
jmp hlt_loop


/* This is the default interrupt "handler" :-) */
ALIGN
ignore_int:              //默认的中断处理程序,打印一段字符。
cld
#ifdef CONFIG_PRINTK
pushl %eax
pushl %ecx
pushl %edx
pushl %es
pushl %ds
movl $(__KERNEL_DS),%eax
movl %eax,%ds
movl %eax,%es
cmpl $2,early_recursion_flag           //最多发生两次这样的中断。。。
je hlt_loop
incl early_recursion_flag
pushl 16(%esp)
pushl 24(%esp)
pushl 32(%esp)
pushl 40(%esp)
pushl $int_msg
call printk

call dump_stack

addl $(5*4),%esp
popl %ds
popl %es
popl %edx
popl %ecx
popl %eax
#endif
iret


#include "verify_cpu.S"


__REFDATA
.align 4
ENTRY(initial_code)                //最终内核要跳转到的地方:i386_start_kernel
.long i386_start_kernel

/*
 * BSS section
 */
__PAGE_ALIGNED_BSS
.align PAGE_SIZE
#ifdef CONFIG_X86_PAE
initial_pg_pmd:                   
.fill 1024*KPMDS,4,0
#else
ENTRY(initial_page_table)          //全局页目录表
.fill 1024,4,0
#endif
initial_pg_fixmap:                 //全局页目录表最后一项对应的页表
.fill 1024,4,0
ENTRY(empty_zero_page)             //空页
.fill 4096,1,0
ENTRY(swapper_pg_dir)              
.fill 1024,4,0


/*
 * This starts the data section.
 */
#ifdef CONFIG_X86_PAE
__PAGE_ALIGNED_DATA
/* Page-aligned for the benefit of paravirt? */
.align PAGE_SIZE
ENTRY(initial_page_table)
.long pa(initial_pg_pmd+PGD_IDENT_ATTR),0/* low identity map */
# if KPMDS == 3
.long pa(initial_pg_pmd+PGD_IDENT_ATTR),0
.long pa(initial_pg_pmd+PGD_IDENT_ATTR+0x1000),0
.long pa(initial_pg_pmd+PGD_IDENT_ATTR+0x2000),0
# elif KPMDS == 2
.long 0,0
.long pa(initial_pg_pmd+PGD_IDENT_ATTR),0
.long pa(initial_pg_pmd+PGD_IDENT_ATTR+0x1000),0
# elif KPMDS == 1
.long 0,0
.long 0,0
.long pa(initial_pg_pmd+PGD_IDENT_ATTR),0
# else
#  error "Kernel PMDs should be 1, 2 or 3"
# endif
.align PAGE_SIZE/* needs to be page-sized too */
#endif


.data
.balign 4
ENTRY(stack_start)                 //堆栈的起址地址,这里就是init_task的内核堆栈,这里可以看做它是被创建出来的进程,因为这时候已经对init_task进行了初始化。
.long init_thread_union+THREAD_SIZE

early_recursion_flag:
.long 0


ready: .byte 0

//打印的错误信息
int_msg:
.asciz "Unknown interrupt or fault at: %p %p %p\n"


fault_msg:
/* fault info: */
.ascii "BUG: Int %d: CR2 %p\n"
/* pusha regs: */
.ascii "     EDI %p  ESI %p  EBP %p  ESP %p\n"
.ascii "     EBX %p  EDX %p  ECX %p  EAX %p\n"
/* fault frame: */
.ascii "     err %p  EIP %p   CS %p  flg %p\n"
.ascii "Stack: %p %p %p %p %p %p %p %p\n"
.ascii "       %p %p %p %p %p %p %p %p\n"
.asciz "       %p %p %p %p %p %p %p %p\n"


#include "../../x86/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
.long 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
.quad 0x00cf9a000000ffff/* kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff/* kernel 4GB data at 0x00000000 */



       好了,这段代码算是将关键部分分析了一遍,现在总结起来也就执行了如下的操作:

1、初始化各个段寄存器和GDTR,并且设置堆栈、

2、清除bss段

3、拷贝系统参数

4、设置页表和全局页目录表(其中为用户设置1项,为内核设置所有项)并开启分页模式

5、再次初始化IDT,虽然这仍然不是最终的中断描述符表,但是较之于以前的有了改变

6、检查CPU并保存一些CPU参数、初始化协处理器

7、装载IGTR和GDTR,其中中断描述符表有256项,GDTR暂时只存在4项(在内核空间也只需要2项)

8、跳转到i386_start_kernel处执行。


        终于通过查资料把这段代码自己搞明白了,快5点了,兴奋的一点都不想睡,接下来的代码都是C语言编写的了,所以应该会轻松点了吧,加油!!!

妈的,我算是被CSDN的文本格式搞败了,辛辛苦苦搞了半天才发现发表出来格式一塌糊涂,真的不知道怎么使用,气死了。。。

       

你可能感兴趣的:(Linux,Kernel)