- /*
- *
- * Copyright (C) 1991, 1992 Linus Torvalds
- *
- * Enhanced CPU detection and feature setting code by Mike Jagdis
- * and Martin Mares, November 1997.
- */
- .text
- #include <linux/threads.h>
- #include <linux/init.h>
- #include <linux/linkage.h>
- #include <asm/segment.h>
- #include <asm/page_types.h>
- #include <asm/pgtable_types.h>
- #include <asm/cache.h>
- #include <asm/thread_info.h>
- #include <asm/asm-offsets.h>
- #include <asm/setup.h>
- #include <asm/processor-flags.h>
- #include <asm/msr-index.h>
- #include <asm/cpufeature.h>
- #include <asm/percpu.h>
- /* Physical address */
- #define pa(X) ((X) - __PAGE_OFFSET)
- /*
- * kernel的符号被链接到以__PAGE_OFFSET为基址的地方。
- * __PAGE_OFFSET=0xc0000000,内核线性地址空间在高1G(__PAGE_OFFSET)的位置,
- * 映射的是物理0开始的位置,所以内核线性地址减去__PAGE_OFFSET就是物理地址
- * (PA)=physical address
- */
- /*
- * 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)
- * 物理地址扩展PAE,32位CPU地址线有36位
- * 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
- *
- * ULK第二章中有,x86_32:PTRS_PER_PTE=1024,PTRS_PER_PMD=1,PTRS_PER_PUD=1,
- * PTRS_PER_PGD=1024,上面说了非PAE用else那个宏
- * 下面的宏是算pages个页需要多少个页目录项
- */
- #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)
- #endif
- /*
- * Number of possible pages in the lowmem region
- * 32位情况下,低物理空间包含页数是:
- * 4G-0xc0000000/4k=1G/4k=0x40000个页
- */
- LOWMEM_PAGES = (((1<<32) - __PAGE_OFFSET) >> PAGE_SHIFT)
- /*
- * Enough space to fit pagetables for the low memory linear map
- * 32位下PAGE_SHIFT=12
- * __PAGE_OFFSET=0xc0000000
- *
- * 计算要映射0x40000个页(1G内存)需要多少个页目录项=256
- * 1个页目录项(页表)需要4k内存,256个需要1M内存。
- * MAPPING_BEYOND_END = PAGE_TABLE_SIZE(0x40000) << 12=256<<12=
- * 0x100 000=1M。算出来这个值是低1G物理内存需要1M空间来存放页表
- */
- 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
- 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.
- * esi指向的是bootloader给的boot_params
- BP_loadflags(esi)指向boot_params.loadflags.此位在code32_start hook中使用
- 因为在protected_mode_jump的最后阶已经将所有的段都设置为_BOOT_DS了。如果
- bootloader hook了code32_start,返回kernel的时候显然kernel需要去恢复所有的段
- */
- __HEAD
- ENTRY(startup_32)
- movl pa(stack_start),%ecx
- /* test KEEP_SEGMENTS flag to see if the bootloader is asking
- us to not reload segments 是否恢复所有段,BP_意思就是Boot_Params_*/
- testb $(1<<6), BP_loadflags(%esi)
- jnz 2f
- /*
- * Set segments to known values.
- * 设置gdtr为boot_gdt_descr(本文后面定义)并设置所有段为_BOOT_DS
- */
- 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:
- /* 本文件入口处设置了ecx为栈开始处*/
- leal -__PAGE_OFFSET(%ecx),%esp
- /*
- * Clear BSS first so that there are no surprises...
- * 清bss
- */
- cld
- xorl %eax,%eax
- movl $pa(__bss_start),%edi
- movl $pa(__bss_stop),%ecx
- subl %edi,%ecx
- shrl $2,%ecx
- 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.
- * esi还指向实模式中boot_params,将它拷贝到edi处
- */
- movl $pa(boot_params),%edi
- movl $(PARAM_SIZE/4),%ecx
- cld
- rep
- movsl
- /* 复制命令行参数*/
- movl pa(boot_params) + NEW_CL_POINTER,%esi
- 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
- /*
- * 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 */
- ////////////////not PAE////////////////////////////////////////
- page_pde_offset = (__PAGE_OFFSET >> 20); /*=0x0c00*/
- /*
- * __brk_base是页表首地址
- * edi=页表首地址
- * edx=初始页目录地址
- * /include/asm/pgtable_types.h:
- * PTE_IDENT_ATTR=0x003(PRESENT+RW)
- * PDE_IDENT_ATTR=0x067(PRESENT+RW+USER+DIRTY+ACCESSED)
- * PGD_IDENT_ATTR=0x001(PRESENT (no other attributes)
- */
- movl $pa(__brk_base), %edi /*页表首地址:各页表从这里开始存放*/
- movl $pa(initial_page_table), %edx /*页目录首址:所谓的临时页全局目录*/
- movl $PTE_IDENT_ATTR, %eax /*页表项属性*/
- /*
- * 虽然现在是4级分页模式,但我们假设现在是未开PAE的32版本,所以,则于
- * 4级与以前2级分页模式兼容,对比着2级模式来学习:
- * 1. 每个活动进程有一个页目录
- * 2. 当前进程的页目录"物理地址"存放在cr3寄存器中
- * 3. 32位线性地址被分为3个部分:Directory[10]-Table[10]-Offset[12]
- * 4. cr3指向页目录表,表项为各Table物理地址,Table表项为页的地址
- * 这两种表项的结构相同,低12位作为属性,高20位是物理地址。
- * 5. 用Directory字段作为索引在cr3所指位置开始查Table的物理地址
- * 用Table字段在上一步的页表中查,找到具体的页的物理地址
- * 6. offset找到页中偏移的字节
- */
- /* edi(_brk_base)是页表首址,用这个地址+页目录属性=
- * 说明这个页目录是指向第1个页表的。属性放在低12位上,高20位放在页目录里,
- * 作为页目录的一项。即:页目录的这(第)一项的高20位指向第一个页表。它就是
- * ULK第二章中的pg0(第0个页表)
- */
- 10:
- leal PDE_IDENT_ATTR(%edi),%ecx /* Create PDE entry */
- /*
- * edx指向的是页目录地址,将刚才做好的页目录第一项放入页目录开始处。
- */
- movl %ecx,(%edx) /* Store identity PDE entry */
- /*
- * 上面知道page_pde_offset=0x0c00,所以下面这句的意思是,edx偏移0c00处
- * 的页目录项(指向一个页表)也设置成刚才的值。那么0xc00是第几项呢?因为
- * 一项大小是4字节,那么它应该是第0x300(768)项。
- * 实模式是从物理地址0开始访问的,而保护模式以__PAGE_OFFSET=0xC0000000
- * (高1G空间)作为内核开始地址的。在开启分页后,把物理地址0~XM映射到两份,
- * 一份是线性地址0~XM,另一份是线性地址__PAGE_OFFSET~__PAGE_OFFSET+XM。
- * 每块物理内存都被映射到两个线性地址上,所以要放到页目录中两次以映射
- * 到两个线性地址范围。这样的话:
- * 1. 目前通过pm.c我们已经处于保护模式下了,但未开启分页
- * 2. 没开分页的时候我们访问的地址是直接的物理地址,如想访问0~X,直接访问
- * 3. 开分页的话,访问物理低1G时,需要通过线性地址0xc0000000开始访问.
- * 在实模式要访问低物理地址的话通过0开始访问(但前面已经是保护模式了,何
- * 来实模式???)
- */
- movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
- /*
- * 下面这句是给edx+4,让edx指向页目录下一项,刚才是第0项和第0x300(768)项,那现
- * 在就是第1项和第0x300(769)项。
- */
- addl $4,%edx
- /*
- * 关于页项表:
- * 页表的每一项覆盖4K内存,其形式是[高20位是页物理地址][低12位是属性],在寻址
- * 时,通过Table字段在页表中找到页表项后,得到一个页的高20位物理地址,然后加上
- * 线性地址Offset字段来找到具体字节。现在,对于0号页表(映射0~4M物理内存),它的
- * 第0项指向0-4K地址,值应该是[0x00000-???],第一项应该是[0x00001-???],第
- * 二项是[0x00002-???]...以此类推,问号部分是页属性(低12位)。
- * 所以,则于edi的值现在还是__brk_base(页表首址),eax是页表项属性,第一次是给
- * 第0项设置值,直接加上属性eax即可,第二次是给第1项设置,则需要给bit12加上1,即
- * 给整个4字节数加上0x1000。一个页表1024项,所以ecx=1024.
- */
- movl $1024, %ecx
- 11:
- stosl /* eax->[edi],edi = edi + 4 */
- addl $0x1000,%eax
- loop 11b
- /*
- * End condition: we must map up to the end + MAPPING_BEYOND_END.
- * eax初始值是PTE_IDENT_ATTR,下面ebp也包括这个值,那么两寄存器除去属性后就是
- * 两个地址范围了,eax不断从0向高地址生长,如果没超过ebp所指的界限的话,则需要
- * 继续映射。前面,edx已经加4了,说明标号10每循环一次,就是处理一个页目录项、亦
- * 即一个页表、1024个页。
- */
- movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
- cmpl %ebp,%eax
- jb 10b
- /*
- * edi+=0xc0000000,这样的话,假设edi原来是X,开分页后访问物理地址X需要经过地址
- * 映射,将访问的0xc0000000+X转换成X.
- */
- addl $__PAGE_OFFSET, %edi
- movl %edi, pa(_brk_end) /* edi是最后一个页表项的后面放入变量中*/
- shrl $12, %eax /* eax>>12是刚才所映射的页框个数,存入变量中*/
- 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_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
- 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
- andl %edx,%edx
- jz 6f /* 去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 /* 下面3句开分页*/
- orl $X86_CR0_PG,%eax
- movl %eax,%cr0 /* ..and set paging (PG) bit */
- /*
- * Clear prefetch and normalize %eip
- * 下面用long jmp 来装入__BOOT_CS.
- */
- ljmp $__BOOT_CS,$1f
- 1:
- /*
- * Shift the stack pointer to a virtual address
- * 将栈实模式地址转成虚拟机线性地址
- */
- addl $__PAGE_OFFSET, %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.
- * eflags初始化
- */
- pushl $0
- 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
- checkCPUtype:
- 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.
- * 检查486还是386
- */
- 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
- call check_x87 /* 协处理器*/
- lgdt early_gdt_descr /* 加载GDT,这是开分页后的一(总第3)次*/
- lidt idt_descr /* 前面call setup_idt,现在加载之*/
- ljmp $(__KERNEL_CS),$1f /* 带段长跳转*/
- 1: movl $(__KERNEL_DS),%eax # reload all the segment registers
- movl %eax,%ss /* 到此ss都指向内核数据段*/
- movl $(__USER_DS),%eax # DS/ES contains default USER segment
- movl %eax,%ds
- movl %eax,%es /* ds,es指向数据段__USER_DS*/
- /* fs指向每cpu变量*/
- 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
- xorl %eax,%eax # Clear LDT
- lldt %ax /*LDT置0*/
- cld # gcc2 wants the direction flag cleared at all times
- pushl $0 # fake return address for unwinder
- movb $1, ready /* ready 变量设置为1*/
- /* i386_start_kernel@arch/x86/kernel/head32.c*/
- 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.
- * IDT中的表项,intel规定有三种:任务门,中断门和陷阱门,而linux
- * 根据DPL不同又在这三种基础上多加了两种---系统门和系统中断门
- * 但它们的结构是相同的,只是类型字段不同而已,如中断门:
- * 63 ..................4847...................32
- * 偏移量(16-31) + 类型和保留位
- * 段选择符 偏移量(0-15)
- * 31...................1615....................0
- * 其中段选择子是GDT中的索引,指明处理函数位置,寻址都是需要通过
- * GDT的。
- */
- /*
- * 下面4句中,EAX高16位存放段选择子CS,低16位是中断服务程序
- * ignore_int偏移量的0-15bit。
- * EDX高16位是中断服务程序ignore_int偏移量16-31bit。低16位
- * 是类型和保留位
- * 所以:EAX是中断描述符的低4字节,EDX是高4字节,它们被用在下面
- * 的rp_sidt中。这段代码初始化idt_table所指的中断描述符表,虽然
- * 中断服务函数都是默认值。
- */
- 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 */
- lea idt_table,%edi
- mov $256,%ecx /* for循环,i=ecx=256*/
- rp_sidt:
- movl %eax,(%edi) /* 中断描述符的低4字节*/
- movl %edx,4(%edi) /* 中断描述符的高4字节*/
- addl $8,%edi /* 指向下一表项*/
- dec %ecx /* i-- */
- jne rp_sidt /* for 循环*/
- /*
- * 下面定义一个宏,用来设置陷阱门的处理函数
- * handler是处理函数,trapno是IDT索引,也是中断号
- * 宏代码跟上面初始化代码类似
- */
- .macro set_early_handler 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
- /* 调用那个宏来设置IDT中特殊的项,你懂英语的*/
- set_early_handler handler=early_divide_err,trapno=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 /*setup_idt结束了*/
- /*
- * 下面就是那个宏用来的几个中断处理函数
- */
- 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" :-)
- * 默认的中断处理程序,保存环境,调用printk打印
- * int_msg=Unknown interrupt or fault at %p %p %p
- */
- 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)
- .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)
- .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 "http://www.cnblogs.com/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:
- * 低2字节:GDT大小(in bytes),高4字节GDT基址
- * 低2字节:IDT大小,高4字节IDT基址
- */
- .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
- /* 初始的GDT*/
- boot_gdt_descr:
- .word __BOOT_DS+7
- .long boot_gdt - __PAGE_OFFSET
- .word 0 # 32-bit align idt_desc.address
- /* 初始的IDT*/
- 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
- /*
- * 这个在checkCPUtype中用到,在boot_gdt_descr以后。
- * arch/x86/kernel/cpu/common.c中编译阶段会初始化gdt_page
- * 这个文件中定义了EXPORT_PER_CPU_SYMBOL_GPL(gdt_page);
- * DEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page)=
- * { .gdt = {....
- * 宏声明了一个变量gdt_page,等号后面赋值。这个GDT在编译阶段
- * 就被初始化好了,所以现在直接用。
- * struct gdt_page在arch/x86/include/asm/desc.h中
- */
- 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.
- * 参考GDT表项结构可知,下面两个表项的base=0,limit=0xffff即
- * 以0为基址,范围是0xffff的代码段和数据段
- */
- .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 */
- /*
- * 总结:除去PAE、虚拟化、SMP代码外,这个文件做了几件重要的事:
- * 1. 映射内存
- * 2. 初始化GDT
- * 3. 初始化IDT
- * 4. 开分页
- * 关于中断:
- * 中断一般都用APIC,系统中有两部分:一个是负责外设中断的IOAPIC,另一
- * 个是每个CPU中的LOCAL APIC,IOAPIC相当于路由器,它把外设中断信号发给
- * 各CPU中的LOCAL APIC。
- * GDT:
- * GDT经历了3次初始化,pm中一次,本文件两次boot_gdt_descr和early_gdt_descr
- */
转载于:https://www.cnblogs.com/cybertitan/archive/2012/10/10/2719024.html