i386 head.S完整分析【转】

来源:互联网 发布:采购业务数据字典 编辑:程序博客网 时间:2024/06/16 00:35
/* 
* linux/arch/i386/head.S -- the 32-bit startup code. 

* Copyright (C) 1991, 1992 Linux Torvalds 

* Enhanced(增强的,提高的) CPU detection and feature setting code by Mike Jagdis 
* and Martin Mares, Noveber 1997. 
*/ 

.text 
#include <linux/config.h> 
#include <linux/threads.h> 
#include <linux/linkage.h> 
/* 
* 在原文件解压后在include目录下并没有asm这个目录,由config时建立一个 
* link到include/asm-i386. 
*/ 
#include <asm/segment.h> 
#include <asm/page.h> 
#include <asm/pgtable.h> 
#include <asm/desc.h> 

#define OLD_CL_MAGIC_ADDR 0x90020 
#define OLD_CL_MAGIC 0xA33F 
#define OLD_CL_BASE_ADDR 0x90000 
#define OLD_CL_OFFSET 0x90022 
#define NEW_CL_POINTER 0x228 /* Relative(相关物) to real mode data */ 

/* 
* References(涉及) to members of the boot_cpu_data structure. 
*/ 
/* 
* boot_cpu_data是cpuinfo_x86结构变量,存储主CPU(即引导的CPU)的特征信息. 
* 见/asm-i386/processo.h 
*/ 
#define CPU_PARAMS SYMBOL_NAME(boot_cpu_data) 
#define X86 CPU_PARAMS + 0 
#define X86_VENDOR CPU_PARAMS + 1 
#define X86_MODEL CPU_PARAMS + 2 
#define X86_MASK CPU_PARAMS + 3 
#define X86_HARD_MATH CPU_PARAMS + 6 
#define X86_CPUID CPU_PARAMS + 8 
#define X86_CAPABILITY CPU_PARAMS + 12 
#define X86_VENDOR_ID CPU_PARAMS + 16 


/* 系统初始化(第一阶段) */ 

/* 
* swapper_pg_dir is the main page directory, address 0x00101000 

* On entry, %esi points to the real-mode code as a 32-bit pointer. 
*/ 

/* 
* 内核映象的起点是stext,也是_stext,引导和解压缩以后的整个映象放在内存中从0x100000即1MB开始的区 
* 间. CPU执行内核映象的入口startup_32在内核映象开头的地方,其物理地址也是0x100000, 其虚拟地址为 
* 0xC0100000. [不是0xC0000000 ??][上面的宏定义不占用空间吗 ??] 
* 在转到startup_32执行时(见arch/i386/compressed/head.S)用长跳转"ljmp $(__KERNEL_CS),$0x100000" 
* 而不是"ljmp $(__KERNEL_CS), startup_32", 所以装入CPU中寄存器IP的地址是物理地址0x100000而不是 
* 虚拟地址0xC0100000,这样CPU在进入startup_32以后就会继续以物理地址取指令. 只要不在代码段中引用 
* 某上地址,例如向某个地址作绝对转移,或者调用某个子程序,就可以一直这样运行下去,而与CS的内容无关. 
*/ 
ENTRY(stext) 
ENTRY(_stext) 
startup_32: 
/* 
* Set segments to known values 
*/ 
cld 
/* 
* 设置段寄存器即%ds,%es,%fs及%gs,让它们采用全局段描述表GDT(临时的) 
* 中下标为3的段描述项,RPL为0. 
*/ 
movl $(__KERNEL_DS), %eax 
movl %eax, %ds 
movl %eax, %es 
movl %eax, %fs 
movl %eax, %gs 
#ifdef CONFIG_SMP 

orw %bx, %bx 
jz 1f 

/* 
* 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.. 
*/ 
/* mmu_cr4_features见arch/i386/kernel/setup.c和asm-i386/processor.h */ 
#define cr4_bits mmu_cr4_features - __PAGE_OFFSET 
cmpl $0, cr4_bits 
je 3f 
/* 
* 对于次CPU,如果系统支持PSE/PAE,即36位地址模式的话还要相应设置其控制寄存器%cr4. 
* 对于主CPU呢 [??] 
*/ 
movl %cr4, %eax # Turn on paging options (PSE,PAE,..) 
orl cr4_bits, %eax 
movl %eax, %cr4 
jmp 3f 
/* 
* 主CPU建立临时页面映射表 
*/ 
1: 
#endif 

/* 
* 页目录表和页表中的表项采用如下格式: 
* BIT(31-12) BIT(11-9) BIT(8) BIT(7) BIT(6) BIT(5) BIT(4) BIT(3) BIT(2) BIT(1) BIT(0) 
* 物理页码 AVL 0 0 D A 0 0 U/S R/W P 
* AVL字段供软件使用.P存在属性位,1表示表项有效,0表示无效,此时表项中其余各位均可供软件使用. 
*/ 

/* 
* Initialize page tables 

* 将从pg0开始直到empty_zero_page之间的8K字节设置成一个临时的页面映射表.这 
* 个页面表在内存中,由所有CPU公用,所以只由主CPU进行初始化,共可管理8m内存, 
* 这就是Linux内核对内存大小的最低限度要求 

* 页表项的低三位均为1,表示页面为用户页面,可写,并且页面的内容在内存中 
* 注: 这里的edi存放的是物理地址 
*/ 
movl $pg0 - __PAGE_OFFSET, %edi /* initialize page tables */ 
movl $007, %eax /* "007" doesn't mean with right to kill, but 
PRESENT+RW_USER */ 

2: stosl /* AL/AX->[DI] */ 
add $0x1000, %eax 
cmp $empty_zero_page - __PAGE_OFFSET, %edi 
jne 2b 

/* 
* Enable paging 开启页式映射 
*/ 
3: 
/* 
* 将页面目录的物理地址装入控制寄存器%cr3,并把%cr0中的最高位设 
* 置成1,开启CPU的页面映射机制. 
*/ 
movl $swapper_pg_dir - __PAGE_OFFSET, %eax 
movl %eax, %cr3 /* Set the page table pointer.. */ 
movl %cr0, %eax 
orl $0x80000000, %eax 
movl %eax, %cr0 /* .. and set paging (PG) bit */ 
/* 
* 相对转移指令,从逻辑上说不起什么作用,但是它起到了丢弃已经在CPU的取指令流水 
* 线中内容的作用,这是Intel在i386技术资料中建议的.对两个jmp的说明见P674[!!]. 
*/ 
jmp 1f 
1: 
movl $1f, %eax 
jmp *%eax /* make sure eip is relocated */ 
1: 
/* Set up the stack pointer */ 
/* 
* 共用代码,主CPU与次CPU把系统空间堆栈设置在同一地方,不会引起冲突.这两个页面的 
* 使用只是暂时的,在同一时间中只可能有一个CPU在使用,而且用完了就不会再回来. 
*/ 
lss stack_start, %esp 

#ifdef CONFIG_SMP 
orw %bx, %bx 
jz 1f /* Initial CPU cleans BSS */ 
/* 
* 将次CPU"标志寄存器"设置成0 
*/ 
pushl $0 
popfl 
jmp checkCPUtype 
1: 
#endif CONFIG_SMP 

/* 
* Clear BSS first so that there are no surpries... 
* No need to cld as DF is already clear from cld above... 
*/ 
/* 
* 主CPU初始化内核的bss段.内核的映象跟其他的可执行程序一样有个bss段,bss段中是一些 
* 全局变量或静态变量(应是未初始化的), 需要在开始运行程序的主体之前将这个区间全部 
* 清0.下面把从__bss_start开始到_end为止的bss段全部清0. 
* 注: 像__bss_start,_end这些符号的值是由gcc在编译和连接时自动生成的 
*/ 
xorl %eax, %eax 
movl $SYMBOL_NAME(__bss_start), %edi 
movl $SYMBOL_NAME(_end), %ecx 
subl %edi, %ecx 
rep 
stosb 

/* 
* start system 32-bit setup. We need to re-do some of the things done 
* in 16-bit mode for the "real" operations. 
*/ 
/* 设置初始状态的中断向量表,或说中断描述表.每个表项大小是8个字节,共有256个表项. */ 
call setup_idt 

/* 
* Initialize eflags. Some BIOS's leave bits like NT set. This would 
* confuse the debugger if this code is traced. 
* XXX - best to initialize befor switching to protected mode. 
*/ 
pushl $0 
popfl 

/* 
* Copy bootup parameters out of the way. First 2kB of 
* empty_zero_page is for boot parameters, second 2kB 
* is for the command line. 

* Note: %esi still has the pointer to the real-mode data. 
* 至此esi=0x90000,指向实模式下取得的参数表 
*/ 
/* 
* 现在要把bootsect和setup代码中的2k东西弄过来,因为里边由一些参数还有用,它们都在前2k的地方 
* 参数表信息见arch/i386/kernel/setup.c 
*/ 
movl $SYMBOL_NAME(empty_zero_page), %edi 
movl $512, %ecx 
cld 
rep 
movsl /* [SI]->[DI] 把0x90000开始处的2KB复制到empty_zero_page */ 
xorl %eax, %eax 
movl %512, %ecx 
rep 
stosl /* AL/AX->[DI] 后2KB清0 */ 
movl SYMBOL_NAME(empty_zero_page) + NEW_CL_POINTER, %esi /* 注意: 没$号 */ 
andl %esi, %esi /* 非0说明是新protocol,%esi是command line地址 */ 
jnz 2f # new command line protocol 
/* 
* 这些是处理老引导协议的command line地址并将其也复制到后2KB处 
*/ 
cmpw $(OLD_CL_MAGIC), OLD_CL_MAGIC_ADDR 
jne 1f 
movzwl OLD_CL_OFFSET, %esi 
addl $(OLD_CL_BASE_ADDR), %esi 
2: 
/* 将新的2k command line复制到empty_zero_page + 2048开始处 */ 
movl $SYMBOL_NAME(empty_zero_page) + 2048, %edi 
movl $512, %ecx 
rep 
movsl 
1: 
#ifdef CONFIG_SMP 
/* 
* 检测CPU类型并设置相应CPU信息 
* 问题: 对于设置的CPU,指定的是boot_cpu_data,那么每个次CPU在执行此操作时,不 
* 是都设置在同一个boot_cpu_data的CPU上吗 [??] 
*/ 
checkCPUtype: 
#endif 
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 preserved(保护,保持) eflags. 
*/ 

movl $3, X86 # at least 386 
pushfl # push EFLAGS 
popl %eax # Get EFLAGS 
movl %eax, %ecx # save original EFLAGS 
/* AC位为对准校验位,从486开始出现 */ 
xorl $0x40000, %eax # flip AC bit 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 
and $0x40000, %eax # check if AC bit changed 
je is386 

movl $4, X86, # at least 486 
movl %ecx, %eax 
xorl $0x200000, %eax # check DI flag 
pushl %eax 
popfl # if we are on a straight 486DX, SX, or 
pushfl # 487SX we can not change it 
popl %eax 
xorl %ecx, %eax 
pushl %ecx # restore original EFLAGS 
popfl 
and $0x200000, %eax 
je is486 

/* get vendor info */ 
/* 
* cpuid-0号功能.返回值: 
* EAX=最大功能号 
* EBX:EDX:ECX=CPU厂商识别串 
*/ 
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 

/* 
* cpuid-1号功能.返回值: 
* EAX=CPU说明 
* CPU说明 
* { 
* bit 内容 
* 0--3 修订本(revision) 
* 4--7 型号(model) 
* 8--11 家族(family) 
* } 
* EDX=特征标志字 
* { 
* bit 内容 缩写 
* 0 FPU On-chip FPU 
* 1 Virtual Mode Extension VME 
* 2 Debugging Extension DE 
* 3 Page Size Extension PSE 
* 4 Time Stamp Counter TSC 
* . . . 
* } 
*/ 

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 %cr0, %eax # 486 or better 
andl $0x80000011, %eax # Save PG, PE, PT 
/* 
* AM: 对准屏蔽位(Alignment Mask).与AC位联合使用,决定是否允许执行对准检验 
* WP: 写保护位(Write Protect),用来净化页写保护机构 
* NE: 数值异常事故位,控制处理符点部件中未被屏蔽的异常事故 
* MP: 监视浮点部件位 
*/ 
orl $0x50022, %eax # Set AM, WP, NE and MP 
jmp 2f 

is386: pushl %ecx # restore original EFALGS 
popfl 
movl %cr0, %eax # 386 
and $0x80000011, %eax # Save PG, PE, ET 
orl $2, %eax # set MP 
2: movl %eax, cr0 
/* 测试与CPU配套的浮点协处理器80387是否存在 */ 
call check_x87 

#ifdef CONFIG_SMP 
/* 对CPU进行计数.这样当其计数为1时表示的是主CPU */ 
incb ready 
#endif 
/* 
* 分别设置(每个)CPU的"全局段描述表寄存器"GDTR和"中断描述表寄存器"IDTR.实 
* 际上装入这些寄存器的是一Xgt_desc_struct结构 
* 两个数据结构的空间分配在此文件中,见下. 
*/ 
lgdt gdt_descr 
lidt idt_descr 
ljmp $(__KERNEL_CS), $1f 
/* 
* 由于改变了GDTR的内容,各个段寄存器的内容也要再装入一遍,虽然它们的 
* 内容其实并无改变. 
*/ 
1: movl $(__KERNEL_DS), %eax # reload all the segment registers 
movl %eax, %ds # after changing gdt. 
movl %eax, %es 
movl %eax, %fs 
movl %eax, %gs 

/* 
* 根据是否为多处理器,设置堆栈的指针 
* 问题: 
* 对于多处理器,只加载了ss堆栈段寄存器,那么此时esp没有设置,对于这些CPU用到 
* 的堆栈,系统是如何处理的 [??] 
*/ 
#ifdef CONFIG_SMP 
movl $(__KERNEL_DS), %eax 
movl %eax, %ss # Reload the stack pointer (segment only) 
#else 
/* 对于单处理器,堆栈用原来临时用的stack_start */ 
lss stack_start, %esp # Load processor stack 
#endif 
/* 
* Linux内核并不使用局部段,所以将LDTR置成0. 
* LDTR置0是空段,此时CS指向的必须是GDT表项,即CS中TI位置0 
*/ 
xorl %eax, %eax 
lldt %ax 
cld # gcc2 wants the direction flag cleared at all times 

/* =========================== 至此,初始化的第一阶段已经完成. =========================== */ 

#ifdef CONFIG_SMP 
/* 
* 在SMP系统中,ready对已经执行了初始化第一阶段的CPU进行计数[见上]. 
* 主CPU是首先执行的,所以此时ready为1表明当前CPU是主CPU,否则是次CPU. 主CPU完 
* 成初始化第一阶段后,要调用start_kernel()继续进行第二阶段的初始化.而次CPU直 
* 接通过initialize_secondary()转入其空转进程,不过次CPU的运行要到主CPU基本完 
* 成第二阶段初始化时才会启动,而且主CPU在每启运一个次CPU后都会等待其完成初始 
* 化. 
*/ 
movb ready, %cl 
cmpb $1, %cl 
je 1f # the first CPU calls start_kernel 
# all other CPUs call initialize secondary 
call SYMBOL_NAME(initialize_secondary) /* 见arch/i386/kernel/smpboot.c */ 
jmp L6 
1: 
#endif 
call SYMBOL_NAME(start_kernel) /* 见init/main.c */ 

/* 
* 表面看从start_kernel()或initialize_secondary()返回后就落入一个无限 
* 循环,但其实对这两个函数调用是不会返回的. 
*/ 
L6: 
jmp L6 # main should never return here, but 
# just in case, we know what happens. 
#ifdef CONFIG_SMP 
ready: .byte 0 
#endif 

/* 
* We depend on ET to be correct. This checks for 287/387. 
*/ 
/* 
* ET: 微处理机的摭充类型位(Processor Extension Type),其内保存着微处理机扩 
* 充类型的信息.如果ET=0,表示系统内用的是80387浮点协处理器. 这样设计人员可 
* 以选择使用80287和80387 
* TS: 任务转换位(Task Switched).当一个任务转换完成之后自动把它置成1. 随着 
* TS被置成1,协处理器的操作码将会引起一个协处理器不能使用的陷阱 
* EM: 模拟协处理器位(Emulate Coprocessor).如果EM=1,所有协处理器的操作码都 
* 将产生"协处理器不能使用"的出错信号, 如果EM=0,那么所有协处理器的操作码都 
* 能在协处理80287或80387上执行 
*/ 
/* 
* clts指令说明: 
* CLTS - Clear Task Switched Flag (286+ privileged) 
* Usage: CLTS 
* Modifies flags: None 
* Clears the Task Switched Flag in the Machine Status Register. This 
* is a privileged operation and is generally used only by operating 
* system code. 
*/ 
/* 
* 如系统中有FPU,则初始化它.如没有FPU,则设置控制寄存器CR0的EM标志,表示 
* 用软件模拟浮点运算. 
*/ 
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. 
*/ 
/* 
* 门描述符格式: 
* m+7 | m+6 | m+5 | m+4 | m+3 | m+2 | m+1 | m+0 | 
* 0ffset(31...16) | Attribute | Selector | Offset(15..0) 

* Byte m+5 | Byte m+4 
* B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 
* P | DPL | DT0 | TYPE | 000 | Dword Count 
*/ 
setup_idt: 
/* 初始的256个中断描述项全相同,都指向同一中断响应程序ignore_int() */ 
lea ignore_int, %edx 
movl $(__KERNEL_CS << 16), %eax 
movw %dx, %ax /* selecror = 0x0010 = cs */ 
/* 0x8E00: P位为1(在内存),DPL为0(级别最高),D为1(32位),类型码为110(中断门) */ 
movw $0x8E00, %dx /* interrupt gate - dpl=0, present */ 

lea SYMBOL_NAME(idt_table), %edi /* 见arch/i386/kernel/trap.c */ 
mov $256, %ecx 
rp_sidt: 
movl %eax, (%edi) 
movl %edx, 4(%edi) 
addl $8, %edi 
dec %ecx 
jne rp_sidt 
ret 

ENTRY(stack_start) 
.long SYMBOL_NAME(init_task_union) + 8192 /* 见arch/i386/kernel/init_task.c */ 
.long __KERNEL_DS 


/* This is the default interrupt "handler" :-) */ 
int_msg: 
.asciz "Unknown interrupt\n" 
ALIGN 
/* 
* 就是说如发生中断就通过printk()在屏幕上显示一行出错信息.在初始化期间, 
* printk()在屏幕上显示信息,而在系统转入正式运行以后则一般将信息写入系 
* 统的运行日志/var/messages. 
*/ 
ignore_int: 
cld 
pushl %eax 
pushl %ecx 
pushl %edx 
pushl %es 
pushl %ds 
movl $(__KERNEL_DS), %eax 
movl %eax, %ds 
movl %eax, %es 
pushl $int_msg 
call SYMBOL_NAME(printk) 
popl %eax 
popl %ds 
popl %es 
popl %edx 
popl %ecx 
popl %eax 
iret 

/* 
* The interrupt descriptor table has room for 256 idt's, 
* the global descriptor table is dependent on the number 
* of tasks we can have.. 
*/ 
/* 
* 问题: 
* 在asm-i386/desc.h与arch/i386/kernel/traps.c中定义了各种相关的信息, 
* 它们之间是如何建立关系的,语法也不清楚 [??] 
*/ 
#define IDT_ENTRIES 256 
#define GDT_ENTRIES (__TSS(NR_CPUS)) 


.globl SYMBOL_NAME(idt) 
.globl SYMBOL_NAME(gdt) 

ALIGN 
.word 0 
idt_descr: 
.word IDT_ENTRIES * 8 - 1 # idt contains 256 entries 
SYMBOL_NAME(idt): 
.long SYMBOL_NAME(idt_table) 

.word 0 
gdt_descr: 
.word GDT_ENTRIES * 8 - 1 
SYMBOL_NAME(gdt): 
.long SYMBOL_NAME(gdt_table) 

/* 
* This is initialized to create an identity-mapping(恒等映射) at 0-8M (for bootup 
* purposes) and another mapping of the 0-8M area at virtual address 
* PAGE_OFFSET. 
*/ 
.org 0x1000 
/* 
* 一个页面目录的大小是4KB,含有1024个目录项,共代表着4GB的虚存空间.Linux内核以3GB为界 
* 把整个虚存空间分成用户空间和系统空间两部分. 所以页面目录中的低768个目录项用于用户 
* 空间的映射,而高256个目录项用于系统空间的映射.在初始的页面目录swapper_pg_dir中, 用 
* 户空间和系统空间都只映射了开头的两个目录项,即8MB的空间,而且有着相同的映射, 即指向 
* 相同的页面映射表 

* CPU转入系统空间后,应把低区的映射清除. s_p_d以后扩充后将成为所有内核线程的页面映射 
* 目录.在内核线程的正常运行中,处于系统状态的CPU是不应该通过用户空间的虚拟地址访问内 
* 存的.如果发生CPU在内核中通过用户空间的虚拟地址访问内存,会因为产生页面异常而抓住这 
* 个错误. 注意系统态下页面异常的处理,见arch/i386/mm/fault.c. 
*/ 
ENTRY(swapper_pg_dir) 
/* 
* 在虚存空间的低区,即本来应属于用户空间的位置上也放上同样的目录项,是为了平稳过渡 
* 具体见P672 [!!] 
*/ 
.long 0x00102007 
.long 0x00103007 
/* 
* BOOT_USER_PGD_PTRS = 768, BOOT_KERNEL_PGD_PTRS = 256.见asm-i386/pgtable.h 
* 填入766个目录项,每个目录项的大小为4个字节,内容为0 
*/ 
.fill BOOT_USER_PGD_PTRS - 2,4,0 
/* default: 766 entries */ 
/* 指向页面表pg0与pg1 */ 
.long 0x00102007 
.long 0x00103007 
/* default: 254 entries */ 
.fill BOOT_KERNEL_PGD_PTRS - 2,4,0 

/* 
* The page tables are initialized to only 8MB here - the final page 
* tables are set up later depending on memory size. 
*/ 
.org 0x2000 
ENTRY(pg0) 

.org 0x3000 
ENTRY(pg1) 

/* 
* empty_zero_page must immediately follow the page tables ! (The 
* initialization loop counts until empty_zero_page) 
*/ 

.org 0x4000 
ENTRY(empty_zero_page) 

.org 0x5000 
ENTRY(empty_bad_page) 

.org 0x6000 
ENTRY(empty_bad_pte_table) 

#if CONFIG_X86_PAE 

.org 0x7000 
ENTRY(empty_bad_pmd_table) 

.org 0x8000 

#else 

.org 0x7000 

#endif 

/* 
* This starts the data section. Note that the above is all 
* in the text section because it has alignment requirements 
* that we cannot fulfill(完成) any other way. 
*/ 
/* 
* 上面的信息我们将其都放在"text"段是因为我们有对齐的要求 
*/ 
.data 

/* 存储段描述符的格式: 
* m+7 | m+6 | m+5 | m+4 | m+3 | m+2 | m+1 | m+0 
* Base(31...24)| Attributes | Base(23...0) | Segment Limits(15...0) 

* Byte m+6 | Byte m+5 
* B7 | B6 | B5 | BIT4 | B3 | B2 | B1 | B0 | B7 | B6 | B5 | BIT4 | B3 | B2 | B1 | B0 
* G | D | 0 | AVL | Limit(19...16) | P | DPL | DT1 | TYPE 
*/ 

ALIGN 
/* 
* This contains typically 140 quadwords(填补空铅), depending on NR_CPUS. 

* NOTE! Make sure the gdt descriptor in head.S matches this if you 
* change anything. 
*/ 
ENTRY(gdt_table) 
/* 
* GDT中第一项(下标为0)是不用的,这是为了防止在加电后段寄存器未经初始 
* 化就进入保护模式并使用GDT,是Intel规定的. 
*/ 
.quad 0x0000000000000000 /* NULL descriptor */ 
.quad 0x0000000000000000 /* not used */ 
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */ 
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */ 
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */ 
.quad 0x00cff2000000ffff /* 0x2b user 4GB data at 0x00000000 */ 
.quad 0x0000000000000000 /* not used */ 
.quad 0x0000000000000000 /* not used */ 
/* 
* The APM segments have byte granularity and their bases 
* and limits are set at run time. 
*/ 
/* APM BIOS保留 | APM BIOS代码段 | APM BIOS16位代码段 | APM BIOS数据段 */ 
.quad 0x0040920000000000 /* 0x40 APM set up for bad BIOS's */ 
.quad 0x00409a0000000000 /* 0x48 APM CS code */ 
.quad 0x00009a0000000000 /* 0x50 APM CS 16 code (16 bit) */ 
.quad 0x0040920000000000 /* 0x58 APM DS data */ 
.fill NR_CPUS * 4, 8, 0 /* space for TSS's and LDT's */ 

/* 
* This is to aid(帮助) debugging, the various(不同的) locking macros will be putting 
* code fragments(片段) here. When and oops occurs we'd rather know that it's 
* inside the .text.lock section rather than as some offset from whatever 
* function happens to be last in the .text segment. 
*/ 
.section .text.lock 
ENTRY(stext_lock)
0 0