/*
* 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)
* 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)