linux-boot-arch_x86_kernel_head_32.S

原文链接:http://www.cnblogs.com/cybertitan/archive/2012/10/10/2719024.html  

 

  1. /* 
  2.  * 
  3.  *  Copyright (C) 1991, 1992  Linus Torvalds 
  4.  * 
  5.  *  Enhanced CPU detection and feature setting code by Mike Jagdis 
  6.  *  and Martin Mares, November 1997. 
  7.  */  
  8.   
  9. .text  
  10. #include <linux/threads.h>  
  11. #include <linux/init.h>  
  12. #include <linux/linkage.h>  
  13. #include <asm/segment.h>  
  14. #include <asm/page_types.h>  
  15. #include <asm/pgtable_types.h>  
  16. #include <asm/cache.h>  
  17. #include <asm/thread_info.h>  
  18. #include <asm/asm-offsets.h>  
  19. #include <asm/setup.h>  
  20. #include <asm/processor-flags.h>  
  21. #include <asm/msr-index.h>  
  22. #include <asm/cpufeature.h>  
  23. #include <asm/percpu.h>  
  24.   
  25. /* Physical address */  
  26. #define pa(X) ((X) - __PAGE_OFFSET)  
  27. /*   
  28.  * kernel的符号被链接到以__PAGE_OFFSET为基址的地方。 
  29.  * __PAGE_OFFSET=0xc0000000,内核线性地址空间在高1G(__PAGE_OFFSET)的位置, 
  30.  *  映射的是物理0开始的位置,所以内核线性地址减去__PAGE_OFFSET就是物理地址 
  31.  *  (PA)=physical address 
  32.  */  
  33.   
  34. /* 
  35.  * References to members of the new_cpu_data structure. 
  36.  */  
  37.   
  38. #define X86     new_cpu_data+CPUINFO_x86  
  39. #define X86_VENDOR  new_cpu_data+CPUINFO_x86_vendor  
  40. #define X86_MODEL   new_cpu_data+CPUINFO_x86_model  
  41. #define X86_MASK    new_cpu_data+CPUINFO_x86_mask  
  42. #define X86_HARD_MATH   new_cpu_data+CPUINFO_hard_math  
  43. #define X86_CPUID   new_cpu_data+CPUINFO_cpuid_level  
  44. #define X86_CAPABILITY  new_cpu_data+CPUINFO_x86_capability  
  45. #define X86_VENDOR_ID   new_cpu_data+CPUINFO_x86_vendor_id  
  46.   
  47. /* 
  48.  * This is how much memory in addition to the memory covered up to 
  49.  * and including _end we need mapped initially. 
  50.  * We need: 
  51.  *     (KERNEL_IMAGE_SIZE/4096) / 1024 pages (worst case, non PAE) 
  52.  *     (KERNEL_IMAGE_SIZE/4096) / 512 + 4 pages (worst case for PAE) 
  53.  *  物理地址扩展PAE,32位CPU地址线有36位 
  54.  * Modulo rounding, each megabyte assigned here requires a kilobyte of 
  55.  * memory, which is currently unreclaimed. 
  56.  * 
  57.  * This should be a multiple of a page. 
  58.  * 
  59.  * KERNEL_IMAGE_SIZE should be greater than pa(_end) 
  60.  * and small than max_low_pfn, otherwise will waste some page table entries 
  61.  * 
  62.  * ULK第二章中有,x86_32:PTRS_PER_PTE=1024,PTRS_PER_PMD=1,PTRS_PER_PUD=1, 
  63.  * PTRS_PER_PGD=1024,上面说了非PAE用else那个宏 
  64.  * 下面的宏是算pages个页需要多少个页目录项 
  65.  */  
  66.   
  67. #if PTRS_PER_PMD > 1  
  68. #define PAGE_TABLE_SIZE(pages) (((pages) / PTRS_PER_PMD) + PTRS_PER_PGD)  
  69. #else  
  70. #define PAGE_TABLE_SIZE(pages) ((pages) / PTRS_PER_PGD)  
  71. #endif  
  72.   
  73. /*  
  74.  * Number of possible pages in the lowmem region  
  75.  * 32位情况下,低物理空间包含页数是: 
  76.  * 4G-0xc0000000/4k=1G/4k=0x40000个页 
  77.  */  
  78. LOWMEM_PAGES = (((1<<32) - __PAGE_OFFSET) >> PAGE_SHIFT)  
  79.       
  80. /*  
  81.  * Enough space to fit pagetables for the low memory linear map  
  82.  * 32位下PAGE_SHIFT=12 
  83.  * __PAGE_OFFSET=0xc0000000 
  84.  *  
  85.  * 计算要映射0x40000个页(1G内存)需要多少个页目录项=256 
  86.  * 1个页目录项(页表)需要4k内存,256个需要1M内存。 
  87.  * MAPPING_BEYOND_END = PAGE_TABLE_SIZE(0x40000) << 12=256<<12= 
  88.  * 0x100 000=1M。算出来这个值是低1G物理内存需要1M空间来存放页表 
  89.  */  
  90.   
  91. MAPPING_BEYOND_END = PAGE_TABLE_SIZE(LOWMEM_PAGES) << PAGE_SHIFT  
  92.   
  93. /* 
  94.  * Worst-case size of the kernel mapping we need to make: 
  95.  * a relocatable kernel can live anywhere in lowmem, so we need to be able 
  96.  * to map all of lowmem. 
  97.  */  
  98. KERNEL_PAGES = LOWMEM_PAGES  
  99.   
  100. INIT_MAP_SIZE = PAGE_TABLE_SIZE(KERNEL_PAGES) * PAGE_SIZE  
  101. RESERVE_BRK(pagetables, INIT_MAP_SIZE)  
  102.   
  103. /* 
  104.  * 32-bit kernel entrypoint; only used by the boot CPU.  On entry, 
  105.  * %esi points to the real-mode code as a 32-bit pointer. 
  106.  * CS and DS must be 4 GB flat segments, but we don't depend on 
  107.  * any particular GDT layout, because we load our own as soon as we 
  108.  * can. 
  109.  * esi指向的是bootloader给的boot_params 
  110.     BP_loadflags(esi)指向boot_params.loadflags.此位在code32_start hook中使用 
  111.     因为在protected_mode_jump的最后阶已经将所有的段都设置为_BOOT_DS了。如果 
  112.     bootloader hook了code32_start,返回kernel的时候显然kernel需要去恢复所有的段 
  113.  */  
  114. __HEAD  
  115. ENTRY(startup_32)  
  116.     movl pa(stack_start),%ecx  
  117.       
  118.     /* test KEEP_SEGMENTS flag to see if the bootloader is asking 
  119.         us to not reload segments 是否恢复所有段,BP_意思就是Boot_Params_*/  
  120.     testb $(1<<6), BP_loadflags(%esi)  
  121.     jnz 2f  
  122.   
  123. /* 
  124.  * Set segments to known values. 
  125.  * 设置gdtr为boot_gdt_descr(本文后面定义)并设置所有段为_BOOT_DS 
  126.  */  
  127.     lgdt pa(boot_gdt_descr)  
  128.     movl $(__BOOT_DS),%eax  
  129.     movl %eax,%ds  
  130.     movl %eax,%es  
  131.     movl %eax,%fs  
  132.     movl %eax,%gs  
  133.     movl %eax,%ss  
  134. 2:  
  135.     /* 本文件入口处设置了ecx为栈开始处*/  
  136.     leal -__PAGE_OFFSET(%ecx),%esp  
  137.   
  138. /* 
  139.  * Clear BSS first so that there are no surprises... 
  140.  * 清bss 
  141.  */  
  142.     cld  
  143.     xorl %eax,%eax  
  144.     movl $pa(__bss_start),%edi  
  145.     movl $pa(__bss_stop),%ecx  
  146.     subl %edi,%ecx  
  147.     shrl $2,%ecx  
  148.     rep ; stosl  
  149. /* 
  150.  * Copy bootup parameters out of the way. 
  151.  * Note: %esi still has the pointer to the real-mode data. 
  152.  * With the kexec as boot loader, parameter segment might be loaded beyond 
  153.  * kernel image and might not even be addressable by early boot page tables. 
  154.  * (kexec on panic case). Hence copy out the parameters before initializing 
  155.  * page tables. 
  156.  * esi还指向实模式中boot_params,将它拷贝到edi处 
  157.  */  
  158.     movl $pa(boot_params),%edi  
  159.     movl $(PARAM_SIZE/4),%ecx  
  160.     cld  
  161.     rep  
  162.     movsl  
  163.     /* 复制命令行参数*/  
  164.     movl pa(boot_params) + NEW_CL_POINTER,%esi  
  165.     andl %esi,%esi  
  166.     jz 1f           # No command line  
  167.     movl $pa(boot_command_line),%edi  
  168.     movl $(COMMAND_LINE_SIZE/4),%ecx  
  169.     rep  
  170.     movsl  
  171. 1:  
  172.   
  173. #ifdef CONFIG_OLPC  
  174.     /* save OFW's pgdir table for later use when calling into OFW */  
  175.     movl %cr3, %eax  
  176.     movl %eax, pa(olpc_ofw_pgd)  
  177. #endif  
  178.   
  179. /* 
  180.  * Initialize page tables.  This creates a PDE and a set of page 
  181.  * tables, which are located immediately beyond __brk_base.  The variable 
  182.  * _brk_end is set up to point to the first "safe" location. 
  183.  * Mappings are created both at virtual address 0 (identity mapping) 
  184.  * and PAGE_OFFSET for up to _end. 
  185.  */  
  186. #ifdef CONFIG_X86_PAE  
  187.   
  188.     /* 
  189.      * In PAE mode initial_page_table is statically defined to contain 
  190.      * enough entries to cover the VMSPLIT option (that is the top 1, 2 or 3 
  191.      * entries). The identity mapping is handled by pointing two PGD entries 
  192.      * to the first kernel PMD. 
  193.      * 
  194.      * Note the upper half of each PMD or PTE are always zero at this stage. 
  195.      */  
  196.   
  197. #define KPMDS (((-__PAGE_OFFSET) >> 30) & 3) /* Number of kernel PMDs */  
  198.   
  199.     xorl %ebx,%ebx              /* %ebx is kept at zero */  
  200.   
  201.     movl $pa(__brk_base), %edi  
  202.     movl $pa(initial_pg_pmd), %edx  
  203.     movl $PTE_IDENT_ATTR, %eax  
  204. 10:  
  205.     leal PDE_IDENT_ATTR(%edi),%ecx      /* Create PMD entry */  
  206.     movl %ecx,(%edx)            /* Store PMD entry */  
  207.                         /* Upper half already zero */  
  208.     addl $8,%edx  
  209.     movl $512,%ecx  
  210. 11:  
  211.     stosl  
  212.     xchgl %eax,%ebx  
  213.     stosl  
  214.     xchgl %eax,%ebx  
  215.     addl $0x1000,%eax  
  216.     loop 11b  
  217.   
  218.     /* 
  219.      * End condition: we must map up to the end + MAPPING_BEYOND_END. 
  220.      */  
  221.     movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp  
  222.     cmpl %ebp,%eax  
  223.     jb 10b  
  224. 1:  
  225.     addl $__PAGE_OFFSET, %edi  
  226.     movl %edi, pa(_brk_end)  
  227.     shrl $12, %eax  
  228.     movl %eax, pa(max_pfn_mapped)  
  229.   
  230.     /* Do early initialization of the fixmap area */  
  231.     movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax  
  232.     movl %eax,pa(initial_pg_pmd+0x1000*KPMDS-8)  
  233. #else   /* Not PAE */  
  234. ////////////////not PAE////////////////////////////////////////  
  235.   
  236. page_pde_offset = (__PAGE_OFFSET >> 20);  /*=0x0c00*/  
  237.     /* 
  238.      * __brk_base是页表首地址 
  239.      * edi=页表首地址 
  240.      * edx=初始页目录地址 
  241.      * /include/asm/pgtable_types.h: 
  242.      * PTE_IDENT_ATTR=0x003(PRESENT+RW) 
  243.      * PDE_IDENT_ATTR=0x067(PRESENT+RW+USER+DIRTY+ACCESSED) 
  244.      * PGD_IDENT_ATTR=0x001(PRESENT (no other attributes) 
  245.      */  
  246.     movl $pa(__brk_base), %edi          /*页表首地址:各页表从这里开始存放*/  
  247.     movl $pa(initial_page_table), %edx  /*页目录首址:所谓的临时页全局目录*/  
  248.     movl $PTE_IDENT_ATTR, %eax          /*页表项属性*/  
  249.     /* 
  250.      * 虽然现在是4级分页模式,但我们假设现在是未开PAE的32版本,所以,则于 
  251.      * 4级与以前2级分页模式兼容,对比着2级模式来学习:      
  252.      * 1. 每个活动进程有一个页目录 
  253.      * 2. 当前进程的页目录"物理地址"存放在cr3寄存器中 
  254.      * 3. 32位线性地址被分为3个部分:Directory[10]-Table[10]-Offset[12] 
  255.      * 4. cr3指向页目录表,表项为各Table物理地址,Table表项为页的地址 
  256.      *    这两种表项的结构相同,低12位作为属性,高20位是物理地址。 
  257.      * 5. 用Directory字段作为索引在cr3所指位置开始查Table的物理地址 
  258.      *    用Table字段在上一步的页表中查,找到具体的页的物理地址 
  259.      * 6. offset找到页中偏移的字节          
  260.      */  
  261.       
  262.       
  263.     /* edi(_brk_base)是页表首址,用这个地址+页目录属性= 
  264.      * 说明这个页目录是指向第1个页表的。属性放在低12位上,高20位放在页目录里, 
  265.      * 作为页目录的一项。即:页目录的这(第)一项的高20位指向第一个页表。它就是 
  266.      * ULK第二章中的pg0(第0个页表) 
  267.      */  
  268.   
  269. 10:  
  270.     leal PDE_IDENT_ATTR(%edi),%ecx  /* Create PDE entry */  
  271.     /* 
  272.      * edx指向的是页目录地址,将刚才做好的页目录第一项放入页目录开始处。 
  273.      */  
  274.     movl %ecx,(%edx)            /* Store identity PDE entry */  
  275.     /* 
  276.      * 上面知道page_pde_offset=0x0c00,所以下面这句的意思是,edx偏移0c00处 
  277.      * 的页目录项(指向一个页表)也设置成刚才的值。那么0xc00是第几项呢?因为 
  278.      * 一项大小是4字节,那么它应该是第0x300(768)项。 
  279.      * 实模式是从物理地址0开始访问的,而保护模式以__PAGE_OFFSET=0xC0000000 
  280.      * (高1G空间)作为内核开始地址的。在开启分页后,把物理地址0~XM映射到两份, 
  281.      * 一份是线性地址0~XM,另一份是线性地址__PAGE_OFFSET~__PAGE_OFFSET+XM。    
  282.      * 每块物理内存都被映射到两个线性地址上,所以要放到页目录中两次以映射 
  283.      * 到两个线性地址范围。这样的话: 
  284.      * 1. 目前通过pm.c我们已经处于保护模式下了,但未开启分页 
  285.      * 2. 没开分页的时候我们访问的地址是直接的物理地址,如想访问0~X,直接访问 
  286.      * 3. 开分页的话,访问物理低1G时,需要通过线性地址0xc0000000开始访问. 
  287.      *    在实模式要访问低物理地址的话通过0开始访问(但前面已经是保护模式了,何 
  288.      *    来实模式???) 
  289.      */  
  290.     movl %ecx,page_pde_offset(%edx)     /* Store kernel PDE entry */  
  291.     /* 
  292.      * 下面这句是给edx+4,让edx指向页目录下一项,刚才是第0项和第0x300(768)项,那现 
  293.      * 在就是第1项和第0x300(769)项。 
  294.      */  
  295.     addl $4,%edx  
  296.       
  297.       
  298.     /* 
  299.      * 关于页项表: 
  300.      * 页表的每一项覆盖4K内存,其形式是[高20位是页物理地址][低12位是属性],在寻址 
  301.      * 时,通过Table字段在页表中找到页表项后,得到一个页的高20位物理地址,然后加上 
  302.      * 线性地址Offset字段来找到具体字节。现在,对于0号页表(映射0~4M物理内存),它的 
  303.      * 第0项指向0-4K地址,值应该是[0x00000-???],第一项应该是[0x00001-???],第 
  304.      * 二项是[0x00002-???]...以此类推,问号部分是页属性(低12位)。 
  305.      * 所以,则于edi的值现在还是__brk_base(页表首址),eax是页表项属性,第一次是给 
  306.      * 第0项设置值,直接加上属性eax即可,第二次是给第1项设置,则需要给bit12加上1,即 
  307.      * 给整个4字节数加上0x1000。一个页表1024项,所以ecx=1024.      
  308.      */  
  309.     movl $1024, %ecx  
  310. 11:  
  311.     stosl   /* eax->[edi],edi = edi + 4 */  
  312.     addl $0x1000,%eax  
  313.     loop 11b  
  314.     /* 
  315.      * End condition: we must map up to the end + MAPPING_BEYOND_END. 
  316.      * eax初始值是PTE_IDENT_ATTR,下面ebp也包括这个值,那么两寄存器除去属性后就是 
  317.      * 两个地址范围了,eax不断从0向高地址生长,如果没超过ebp所指的界限的话,则需要 
  318.      * 继续映射。前面,edx已经加4了,说明标号10每循环一次,就是处理一个页目录项、亦 
  319.      * 即一个页表、1024个页。  
  320.      */  
  321.     movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp  
  322.     cmpl %ebp,%eax  
  323.     jb 10b  
  324.     /*  
  325.      * edi+=0xc0000000,这样的话,假设edi原来是X,开分页后访问物理地址X需要经过地址 
  326.      * 映射,将访问的0xc0000000+X转换成X. 
  327.      */   
  328.     addl $__PAGE_OFFSET, %edi    
  329.     movl %edi, pa(_brk_end) /* edi是最后一个页表项的后面放入变量中*/  
  330.     shrl $12, %eax          /* eax>>12是刚才所映射的页框个数,存入变量中*/  
  331.     movl %eax, pa(max_pfn_mapped)  
  332.   
  333.     /* 
  334.      * Do early initialization of the fixmap area  
  335.      * 固定映射部分 
  336.      */  
  337.     movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax  
  338.     movl %eax,pa(initial_page_table+0xffc)  
  339. #endif  
  340. /* 半虚拟化相关*/  
  341. #ifdef CONFIG_PARAVIRT  
  342.     /* This is can only trip for a broken bootloader... */  
  343.     cmpw $0x207, pa(boot_params + BP_version)  
  344.     jb default_entry  
  345.   
  346.     /* Paravirt-compatible boot parameters.  Look to see what architecture 
  347.         we're booting under. */  
  348.     movl pa(boot_params + BP_hardware_subarch), %eax  
  349.     cmpl $num_subarch_entries, %eax  
  350.     jae bad_subarch  
  351.   
  352.     movl pa(subarch_entries)(,%eax,4), %eax  
  353.     subl $__PAGE_OFFSET, %eax  
  354.     jmp *%eax  
  355.   
  356. bad_subarch:  
  357. WEAK(lguest_entry)  
  358. WEAK(xen_entry)  
  359.     /* Unknown implementation; there's really 
  360.        nothing we can do at this point. */  
  361.     ud2a  
  362.   
  363.     __INITDATA  
  364.   
  365. subarch_entries:  
  366.     .long default_entry     /* normal x86/PC */  
  367.     .long lguest_entry      /* lguest hypervisor */  
  368.     .long xen_entry         /* Xen hypervisor */  
  369.     .long default_entry     /* Moorestown MID */  
  370. num_subarch_entries = (. - subarch_entries) / 4  
  371. .previous  
  372. #else  
  373.     /* 跳转到默认入口*/  
  374.     jmp default_entry  
  375. #endif /* CONFIG_PARAVIRT */  
  376.   
  377. /* 
  378.  * Non-boot CPU entry point; entered from trampoline.S 
  379.  * We can't lgdt here, because lgdt itself uses a data segment, but 
  380.  * we know the trampoline has already loaded the boot_gdt for us. 
  381.  * 
  382.  * If cpu hotplug is not supported then this code can go in init section 
  383.  * which will be freed later 
  384.  * 
  385.  */  
  386.   
  387. __CPUINIT  
  388.   
  389. #ifdef CONFIG_SMP  
  390. ENTRY(startup_32_smp)  
  391.     cld  
  392.     movl $(__BOOT_DS),%eax  
  393.     movl %eax,%ds  
  394.     movl %eax,%es  
  395.     movl %eax,%fs  
  396.     movl %eax,%gs  
  397.     movl pa(stack_start),%ecx  
  398.     movl %eax,%ss  
  399.     leal -__PAGE_OFFSET(%ecx),%esp  
  400. #endif /* CONFIG_SMP */  
  401. default_entry:  
  402.   
  403. /* 
  404.  *  New page tables may be in 4Mbyte page mode and may 
  405.  *  be using the global pages.  
  406.  * 
  407.  *  NOTE! If we are on a 486 we may have no cr4 at all! 
  408.  *  So we do not try to touch it unless we really have 
  409.  *  some bits in it to set.  This won't work if the BSP 
  410.  *  implements cr4 but this AP does not -- very unlikely 
  411.  *  but be warned!  The same applies to the pse feature 
  412.  *  if not equally supported. --macro 
  413.  * 
  414.  *  NOTE! We have to correct for the fact that we're 
  415.  *  not yet offset PAGE_OFFSET.. 
  416.  */  
  417. #define cr4_bits pa(mmu_cr4_features)  
  418.     movl cr4_bits,%edx  
  419.     andl %edx,%edx  
  420.     jz 6f           /* 去6f*/  
  421.     movl %cr4,%eax      # Turn on paging options (PSE,PAE,..)  
  422.     orl %edx,%eax  
  423.     movl %eax,%cr4  
  424.   
  425.     testb $X86_CR4_PAE, %al     # check if PAE is enabled  
  426.     jz 6f  
  427.   
  428.     /* Check if extended functions are implemented */  
  429.     movl $0x80000000, %eax  
  430.     cpuid  
  431.     /* Value must be in the range 0x80000001 to 0x8000ffff */  
  432.     subl $0x80000001, %eax  
  433.     cmpl $(0x8000ffff-0x80000001), %eax  
  434.     ja 6f  
  435.   
  436.     /* Clear bogus XD_DISABLE bits */  
  437.     call verify_cpu  
  438.   
  439.     mov $0x80000001, %eax  
  440.     cpuid  
  441.     /* Execute Disable bit supported? */  
  442.     btl $(X86_FEATURE_NX & 31), %edx  
  443.     jnc 6f  
  444.   
  445.     /* Setup EFER (Extended Feature Enable Register) */  
  446.     movl $MSR_EFER, %ecx  
  447.     rdmsr  
  448.   
  449.     btsl $_EFER_NX, %eax  
  450.     /* Make changes effective */  
  451.     wrmsr  
  452.   
  453. 6:  
  454.   
  455. /* 
  456.  * Enable paging 
  457.  */  
  458.     movl $pa(initial_page_table), %eax  
  459.     movl %eax,%cr3  /* set the page table pointer页目录物理地址入cr3 */  
  460.     movl %cr0,%eax  /* 下面3句开分页*/  
  461.     orl  $X86_CR0_PG,%eax  
  462.     movl %eax,%cr0      /* ..and set paging (PG) bit */  
  463.     /*  
  464.      * Clear prefetch and normalize %eip  
  465.      * 下面用long jmp 来装入__BOOT_CS. 
  466.      */  
  467.     ljmp $__BOOT_CS,$1f   
  468. 1:  
  469.     /*  
  470.      * Shift the stack pointer to a virtual address  
  471.      * 将栈实模式地址转成虚拟机线性地址 
  472.      */  
  473.     addl $__PAGE_OFFSET, %esp  
  474.   
  475. /* 
  476.  * Initialize eflags.  Some BIOS's leave bits like NT set.  This would 
  477.  * confuse the debugger if this code is traced. 
  478.  * XXX - best to initialize before switching to protected mode. 
  479.  * eflags初始化 
  480.  */  
  481.     pushl $0  
  482.     popfl  
  483.   
  484. #ifdef CONFIG_SMP  
  485.     cmpb $0, ready  
  486.     jnz checkCPUtype  
  487. #endif /* CONFIG_SMP */  
  488.   
  489. /* 
  490.  * start system 32-bit setup. We need to re-do some of the things done 
  491.  * in 16-bit mode for the "real" operations. 
  492.  */  
  493.     call setup_idt  
  494.   
  495. checkCPUtype:  
  496.   
  497.     movl $-1,X86_CPUID      #  -1 for no CPUID initially  
  498.   
  499. /* check if it is 486 or 386. */  
  500. /* 
  501.  * XXX - this does a lot of unnecessary setup.  Alignment checks don't 
  502.  * apply at our cpl of 0 and the stack ought to be aligned already, and 
  503.  * we don't need to preserve eflags. 
  504.  * 检查486还是386 
  505.  */  
  506.   
  507.     movb $3,X86     # at least 386  
  508.     pushfl          # push EFLAGS  
  509.     popl %eax       # get EFLAGS  
  510.     movl %eax,%ecx      # save original EFLAGS  
  511.     xorl $0x240000,%eax # flip AC and ID bits in EFLAGS  
  512.     pushl %eax      # copy to EFLAGS  
  513.     popfl           # set EFLAGS  
  514.     pushfl          # get new EFLAGS  
  515.     popl %eax       # put it in eax  
  516.     xorl %ecx,%eax      # change in flags  
  517.     pushl %ecx      # restore original EFLAGS  
  518.     popfl  
  519.     testl $0x40000,%eax # check if AC bit changed  
  520.     je is386  
  521.   
  522.     movb $4,X86     # at least 486  
  523.     testl $0x200000,%eax    # check if ID bit changed  
  524.     je is486  
  525.   
  526.     /* get vendor info */  
  527.     xorl %eax,%eax          # call CPUID with 0 -> return vendor ID  
  528.     cpuid  
  529.     movl %eax,X86_CPUID     # save CPUID level  
  530.     movl %ebx,X86_VENDOR_ID     # lo 4 chars  
  531.     movl %edx,X86_VENDOR_ID+4   # next 4 chars  
  532.     movl %ecx,X86_VENDOR_ID+8   # last 4 chars  
  533.   
  534.     orl %eax,%eax           # do we have processor info as well?  
  535.     je is486  
  536.   
  537.     movl $1,%eax        # Use the CPUID instruction to get CPU type  
  538.     cpuid  
  539.     movb %al,%cl        # save reg for future use  
  540.     andb $0x0f,%ah      # mask processor family  
  541.     movb %ah,X86  
  542.     andb $0xf0,%al      # mask model  
  543.     shrb $4,%al  
  544.     movb %al,X86_MODEL  
  545.     andb $0x0f,%cl      # mask mask revision  
  546.     movb %cl,X86_MASK  
  547.     movl %edx,X86_CAPABILITY  
  548.   
  549. is486:  movl $0x50022,%ecx  # set AM, WP, NE and MP  
  550.     jmp 2f  
  551.   
  552. is386:  movl $2,%ecx        # set MP  
  553. 2:  movl %cr0,%eax  
  554.     andl $0x80000011,%eax   # Save PG,PE,ET  
  555.     orl %ecx,%eax  
  556.     movl %eax,%cr0  
  557.   
  558.     call check_x87          /* 协处理器*/  
  559.     lgdt early_gdt_descr    /* 加载GDT,这是开分页后的一(总第3)次*/  
  560.     lidt idt_descr          /* 前面call setup_idt,现在加载之*/  
  561.     ljmp $(__KERNEL_CS),$1f /* 带段长跳转*/  
  562. 1:  movl $(__KERNEL_DS),%eax    # reload all the segment registers  
  563.     movl %eax,%ss           /* 到此ss都指向内核数据段*/  
  564.                               
  565.     movl $(__USER_DS),%eax      # DS/ES contains default USER segment  
  566.     movl %eax,%ds  
  567.     movl %eax,%es           /* ds,es指向数据段__USER_DS*/  
  568.   
  569.     /* fs指向每cpu变量*/  
  570.     movl $(__KERNEL_PERCPU), %eax  
  571.     movl %eax,%fs           # set this cpu's percpu  
  572.   
  573. #ifdef CONFIG_CC_STACKPROTECTOR  
  574.     /* 
  575.      * The linker can't handle this by relocation.  Manually set 
  576.      * base address in stack canary segment descriptor. 
  577.      */  
  578.     cmpb $0,ready  
  579.     jne 1f  
  580.     movl $gdt_page,%eax  
  581.     movl $stack_canary,%ecx  
  582.     movw %cx, 8 * GDT_ENTRY_STACK_CANARY + 2(%eax)  
  583.     shrl $16, %ecx  
  584.     movb %cl, 8 * GDT_ENTRY_STACK_CANARY + 4(%eax)  
  585.     movb %ch, 8 * GDT_ENTRY_STACK_CANARY + 7(%eax)  
  586. 1:  
  587. #endif  
  588.     movl $(__KERNEL_STACK_CANARY),%eax  
  589.     movl %eax,%gs  
  590.   
  591.     xorl %eax,%eax          # Clear LDT  
  592.     lldt %ax                /*LDT置0*/  
  593.   
  594.     cld         # gcc2 wants the direction flag cleared at all times  
  595.     pushl $0        # fake return address for unwinder  
  596.     movb $1, ready  /* ready 变量设置为1*/  
  597.       
  598.     /* i386_start_kernel@arch/x86/kernel/head32.c*/  
  599.     jmp *(initial_code)  
  600.   
  601. /* 
  602.  * We depend on ET to be correct. This checks for 287/387. 
  603.  */  
  604. check_x87:  
  605.     movb $0,X86_HARD_MATH  
  606.     clts  
  607.     fninit  
  608.     fstsw %ax  
  609.     cmpb $0,%al  
  610.     je 1f  
  611.     movl %cr0,%eax      /* no coprocessor: have to set bits */  
  612.     xorl $4,%eax        /* set EM */  
  613.     movl %eax,%cr0  
  614.     ret  
  615.     ALIGN  
  616. 1:  movb $1,X86_HARD_MATH  
  617.     .byte 0xDB,0xE4     /* fsetpm for 287, ignored by 387 */  
  618.     ret  
  619.   
  620. /* 
  621.  *  setup_idt 
  622.  * 
  623.  *  sets up a idt with 256 entries pointing to 
  624.  *  ignore_int, interrupt gates. It doesn't actually load 
  625.  *  idt - that can be done only after paging has been enabled 
  626.  *  and the kernel moved to PAGE_OFFSET. Interrupts 
  627.  *  are enabled elsewhere, when we can be relatively 
  628.  *  sure everything is ok. 
  629.  * 
  630.  *  Warning: %esi is live across this function. 
  631.  *  IDT中的表项,intel规定有三种:任务门,中断门和陷阱门,而linux 
  632.  *  根据DPL不同又在这三种基础上多加了两种---系统门和系统中断门 
  633.  *  但它们的结构是相同的,只是类型字段不同而已,如中断门: 
  634.  *  63 ..................4847...................32 
  635.  *    偏移量(16-31)       +     类型和保留位 
  636.  *    段选择符                  偏移量(0-15) 
  637.  *  31...................1615....................0   
  638.  *  其中段选择子是GDT中的索引,指明处理函数位置,寻址都是需要通过 
  639.  *  GDT的。 
  640.  */  
  641.    
  642.    
  643.  /* 
  644.   * 下面4句中,EAX高16位存放段选择子CS,低16位是中断服务程序 
  645.   * ignore_int偏移量的0-15bit。 
  646.   * EDX高16位是中断服务程序ignore_int偏移量16-31bit。低16位 
  647.   * 是类型和保留位 
  648.   * 所以:EAX是中断描述符的低4字节,EDX是高4字节,它们被用在下面 
  649.   * 的rp_sidt中。这段代码初始化idt_table所指的中断描述符表,虽然 
  650.   * 中断服务函数都是默认值。  
  651.   */  
  652. setup_idt:  
  653.     lea ignore_int,%edx  
  654.     movl $(__KERNEL_CS << 16),%eax    
  655.     movw %dx,%ax        /* selector = 0x0010 = cs */  
  656.     movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */  
  657.   
  658.     lea idt_table,%edi  
  659.     mov $256,%ecx       /* for循环,i=ecx=256*/  
  660. rp_sidt:  
  661.     movl %eax,(%edi)    /* 中断描述符的低4字节*/  
  662.     movl %edx,4(%edi)   /* 中断描述符的高4字节*/  
  663.     addl $8,%edi        /* 指向下一表项*/  
  664.     dec %ecx            /* i-- */  
  665.     jne rp_sidt         /* for 循环*/  
  666.     /* 
  667.      * 下面定义一个宏,用来设置陷阱门的处理函数 
  668.      * handler是处理函数,trapno是IDT索引,也是中断号 
  669.      * 宏代码跟上面初始化代码类似 
  670.      */   
  671. .macro  set_early_handler handler,trapno  
  672.     lea \handler,%edx  
  673.     movl $(__KERNEL_CS << 16),%eax  
  674.     movw %dx,%ax  
  675.     movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */  
  676.     lea idt_table,%edi  
  677.     movl %eax,8*\trapno(%edi)  
  678.     movl %edx,8*\trapno+4(%edi)  
  679. .endm  
  680.       
  681.     /* 调用那个宏来设置IDT中特殊的项,你懂英语的*/  
  682.     set_early_handler handler=early_divide_err,trapno=0  
  683.     set_early_handler handler=early_illegal_opcode,trapno=6  
  684.     set_early_handler handler=early_protection_fault,trapno=13  
  685.     set_early_handler handler=early_page_fault,trapno=14  
  686.   
  687.     ret /*setup_idt结束了*/  
  688.   
  689. /* 
  690.  * 下面就是那个宏用来的几个中断处理函数 
  691.  */  
  692. early_divide_err:  
  693.     xor %edx,%edx  
  694.     pushl $0    /* fake errcode */  
  695.     jmp early_fault  
  696.   
  697. early_illegal_opcode:  
  698.     movl $6,%edx  
  699.     pushl $0    /* fake errcode */  
  700.     jmp early_fault  
  701.   
  702. early_protection_fault:  
  703.     movl $13,%edx  
  704.     jmp early_fault  
  705.   
  706. early_page_fault:  
  707.     movl $14,%edx  
  708.     jmp early_fault  
  709.   
  710. early_fault:  
  711.     cld  
  712. #ifdef CONFIG_PRINTK  
  713.     pusha  
  714.     movl $(__KERNEL_DS),%eax  
  715.     movl %eax,%ds  
  716.     movl %eax,%es  
  717.     cmpl $2,early_recursion_flag  
  718.     je hlt_loop  
  719.     incl early_recursion_flag  
  720.     movl %cr2,%eax  
  721.     pushl %eax  
  722.     pushl %edx      /* trapno */  
  723.     pushl $fault_msg  
  724.     call printk  
  725. #endif  
  726.     call dump_stack  
  727. hlt_loop:  
  728.     hlt  
  729.     jmp hlt_loop  
  730.   
  731. /* 
  732.  * This is the default interrupt "handler" :-)  
  733.  * 默认的中断处理程序,保存环境,调用printk打印 
  734.  * int_msg=Unknown interrupt or fault at %p %p %p 
  735.  */  
  736.     ALIGN  
  737. ignore_int:  
  738.     cld  
  739. #ifdef CONFIG_PRINTK  
  740.     pushl %eax  
  741.     pushl %ecx  
  742.     pushl %edx  
  743.     pushl %es  
  744.     pushl %ds  
  745.     movl $(__KERNEL_DS),%eax  
  746.     movl %eax,%ds  
  747.     movl %eax,%es  
  748.     cmpl $2,early_recursion_flag  
  749.     je hlt_loop  
  750.     incl early_recursion_flag  
  751.     pushl 16(%esp)    
  752.     pushl 24(%esp)  
  753.     pushl 32(%esp)  
  754.     pushl 40(%esp)  
  755.     pushl $int_msg  
  756.     call printk  
  757.   
  758.     call dump_stack  
  759.   
  760.     addl $(5*4),%esp  
  761.     popl %ds  
  762.     popl %es  
  763.     popl %edx  
  764.     popl %ecx  
  765.     popl %eax  
  766. #endif  
  767.     iret  
  768.   
  769. #include "verify_cpu.S"  
  770.   
  771.     __REFDATA  
  772. .align 4  
  773. ENTRY(initial_code)  
  774.     .long i386_start_kernel  
  775.   
  776. /* 
  777.  * BSS section 
  778.  */  
  779. __PAGE_ALIGNED_BSS  
  780.     .align PAGE_SIZE  
  781. #ifdef CONFIG_X86_PAE  
  782. initial_pg_pmd:  
  783.     .fill 1024*KPMDS,4,0  
  784. #else  
  785. ENTRY(initial_page_table)  
  786.     .fill 1024,4,0  
  787. #endif  
  788. initial_pg_fixmap:  
  789.     .fill 1024,4,0  
  790. ENTRY(empty_zero_page)  
  791.     .fill 4096,1,0  
  792. ENTRY(swapper_pg_dir)  
  793.     .fill 1024,4,0  
  794.   
  795. /* 
  796.  * This starts the data section. 
  797.  */  
  798. #ifdef CONFIG_X86_PAE  
  799. __PAGE_ALIGNED_DATA  
  800.     /* Page-aligned for the benefit of paravirt? */  
  801.     .align PAGE_SIZE  
  802. ENTRY(initial_page_table)  
  803.     .long   pa(initial_pg_pmd+PGD_IDENT_ATTR),0 /* low identity map */  
  804. # if KPMDS == 3  
  805.     .long   pa(initial_pg_pmd+PGD_IDENT_ATTR),0  
  806.     .long   pa(initial_pg_pmd+PGD_IDENT_ATTR+0x1000),0  
  807.     .long   pa(initial_pg_pmd+PGD_IDENT_ATTR+0x2000),0  
  808. # elif KPMDS == 2  
  809.     .long   0,0  
  810.     .long   pa(initial_pg_pmd+PGD_IDENT_ATTR),0  
  811.     .long   pa(initial_pg_pmd+PGD_IDENT_ATTR+0x1000),0  
  812. # elif KPMDS == 1  
  813.     .long   0,0  
  814.     .long   0,0  
  815.     .long   pa(initial_pg_pmd+PGD_IDENT_ATTR),0  
  816. # else  
  817. #  error "Kernel PMDs should be 1, 2 or 3"  
  818. # endif  
  819.     .align PAGE_SIZE        /* needs to be page-sized too */  
  820. #endif  
  821.   
  822. .data  
  823. .balign 4  
  824. ENTRY(stack_start)  
  825.     .long init_thread_union+THREAD_SIZE  
  826.   
  827. early_recursion_flag:  
  828.     .long 0  
  829.   
  830. ready:  .byte 0  
  831.   
  832. int_msg:  
  833.     .asciz "Unknown interrupt or fault at: %p %p %p\n"  
  834.   
  835. fault_msg:  
  836. /* fault info: */  
  837.     .ascii "BUG: Int %d: CR2 %p\n"  
  838. /* pusha regs: */  
  839.     .ascii "     EDI %p  ESI %p  EBP %p  ESP %p\n"  
  840.     .ascii "     EBX %p  EDX %p  ECX %p  EAX %p\n"  
  841. /* fault frame: */  
  842.     .ascii "     err %p  EIP %p   CS %p  flg %p\n"  
  843.     .ascii "Stack: %p %p %p %p %p %p %p %p\n"  
  844.     .ascii "       %p %p %p %p %p %p %p %p\n"  
  845.     .asciz "       %p %p %p %p %p %p %p %p\n"  
  846.   
  847. #include "http://www.cnblogs.com/x86/xen/xen-head.S"  
  848.   
  849. /* 
  850.  * The IDT and GDT 'descriptors' are a strange 48-bit object 
  851.  * only used by the lidt and lgdt instructions. They are not 
  852.  * like usual segment descriptors - they consist of a 16-bit 
  853.  * segment size, and 32-bit linear address value: 
  854.  * 低2字节:GDT大小(in bytes),高4字节GDT基址 
  855.  *  低2字节:IDT大小,高4字节IDT基址 
  856.  */  
  857.   
  858. .globl boot_gdt_descr  
  859. .globl idt_descr  
  860.   
  861.     ALIGN  
  862. # early boot GDT descriptor (must use 1:1 address mapping)  
  863.     .word 0             # 32 bit align gdt_desc.address  
  864. /* 初始的GDT*/   
  865. boot_gdt_descr:  
  866.     .word __BOOT_DS+7  
  867.     .long boot_gdt - __PAGE_OFFSET  
  868.   
  869.     .word 0             # 32-bit align idt_desc.address  
  870. /* 初始的IDT*/   
  871. idt_descr:  
  872.     .word IDT_ENTRIES*8-1       # idt contains 256 entries  
  873.     .long idt_table  
  874.   
  875. # boot GDT descriptor (later on used by CPU#0):  
  876.     .word 0             # 32 bit align gdt_desc.address  
  877.       
  878. /* 
  879.  * 这个在checkCPUtype中用到,在boot_gdt_descr以后。 
  880.  * arch/x86/kernel/cpu/common.c中编译阶段会初始化gdt_page 
  881.  * 这个文件中定义了EXPORT_PER_CPU_SYMBOL_GPL(gdt_page); 
  882.  * DEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page)= 
  883.  * { .gdt = {.... 
  884.  * 宏声明了一个变量gdt_page,等号后面赋值。这个GDT在编译阶段 
  885.  * 就被初始化好了,所以现在直接用。 
  886.  * struct gdt_page在arch/x86/include/asm/desc.h中 
  887.  */   
  888. ENTRY(early_gdt_descr)  
  889.     .word GDT_ENTRIES*8-1  
  890.     .long gdt_page          /* Overwritten for secondary CPUs */  
  891.   
  892. /* 
  893.  * The boot_gdt must mirror the equivalent in setup.S and is 
  894.  * used only for booting. 
  895.  * 参考GDT表项结构可知,下面两个表项的base=0,limit=0xffff即 
  896.  * 以0为基址,范围是0xffff的代码段和数据段 
  897.  */  
  898.     .align L1_CACHE_BYTES  
  899. ENTRY(boot_gdt)  
  900.     .fill GDT_ENTRY_BOOT_CS,8,0  
  901.     .quad 0x00cf9a000000ffff    /* kernel 4GB code at 0x00000000 */  
  902.     .quad 0x00cf92000000ffff    /* kernel 4GB data at 0x00000000 */  
  903. /* 
  904.  * 总结:除去PAE、虚拟化、SMP代码外,这个文件做了几件重要的事: 
  905.  * 1. 映射内存 
  906.  * 2. 初始化GDT 
  907.  * 3. 初始化IDT 
  908.  * 4. 开分页 
  909.  * 关于中断: 
  910.  * 中断一般都用APIC,系统中有两部分:一个是负责外设中断的IOAPIC,另一 
  911.  * 个是每个CPU中的LOCAL APIC,IOAPIC相当于路由器,它把外设中断信号发给 
  912.  * 各CPU中的LOCAL APIC。 
  913.  * GDT: 
  914.  * GDT经历了3次初始化,pm中一次,本文件两次boot_gdt_descr和early_gdt_descr 
  915.  */  

转载于:https://www.cnblogs.com/cybertitan/archive/2012/10/10/2719024.html

上一篇:在linux环境中进行AT & T格式的汇编语言demo示例


下一篇:汇编程序的Hello world