- 论坛徽章:
- 0
|
kernel的入口地址是/sys/i386/i386/locore.s中定义的btext:
200 /**********************************************************************
201 *
202 * This is where the bootblocks start us, set the ball rolling...
203 *
204 */
205 NON_GPROF_ENTRY(btext)
从/boot/kernel/kernel中可以读出btext的链接地址:
# readelf -a /boot/kernel/kernel | grep btext
6870: c0458a30 0 FUNC GLOBAL DEFAULT 5 btext
26381: c0458a30 0 FUNC GLOBAL DEFAULT 5 btext
因此,在物理地址0x458a30处设置断点,单步跟踪locore.s中的初始化代码。程序运行至此
的cpu主要寄存器的内容如下:
rax: 0x00000000:00458a30 rcx: 0x00000000:a0200000
rdx: 0x00000000:000488a0 rbx: 0x00000000:00458a30
rsp: 0x00000000:0009e844 rbp: 0x00000000:00094884
rsi: 0x00000000:000610e4 rdi: 0x00000000:0005b9cc
r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
r10: 0x00000000:00000000 r11: 0x00000000:00000000
r12: 0x00000000:00000000 r13: 0x00000000:00000000
r14: 0x00000000:00000000 r15: 0x00000000:00000000
rip: 0x00000000:00458a30
eflags 0x00000002
首先是向0x472写入0x1234,告知bios下次为热引导:
216 /* Tell the bios to warmboot next time */
217 movw $0x1234,0x472
构建一个新的栈帧:
220 /* Set up a real frame in case the double return in newboot is executed. */
221 pushl %ebp
222 movl %esp, %ebp
此时cpu主要寄存器的内容如下:
rax: 0x00000000:00458a30 rcx: 0x00000000:a0200000
rdx: 0x00000000:000488a0 rbx: 0x00000000:00458a30
rsp: 0x00000000:0009e840 rbp: 0x00000000:0009e840
rsi: 0x00000000:000610e4 rdi: 0x00000000:0005b9cc
r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
r10: 0x00000000:00000000 r11: 0x00000000:00000000
r12: 0x00000000:00000000 r13: 0x00000000:00000000
r14: 0x00000000:00000000 r15: 0x00000000:00000000
rip: 0x00000000:00458a3c
eflags 0x00000002
将PSL_KRENEL赋给eflags:
224 /* Don't trust what the BIOS gives for eflags. */
225 pushl $PSL_KERNEL
226 popfl
PSL_KERNEL是在/sys/i386/include/psl.h中定义的:
60 /*
61 * The i486 manual says that we are not supposed to change reserved flags,
62 * but this is too much trouble since the reserved flags depend on the cpu
63 * and setting them to their historical values works in practice.
64 */
65 #define PSL_RESERVED_DEFAULT 0x00000002
66
67 /*
68 * Initial flags for kernel and user mode. The kernel later inherits
69 * PSL_I and some other flags from user mode.
70 */
71 #define PSL_KERNEL PSL_RESERVED_DEFAULT
72 #define PSL_USER (PSL_RESERVED_DEFAULT | PSL_I)
将ds的内容赋给fs和gs:
228 /*
229 * Don't trust what the BIOS gives for %fs and %gs. Trust the bootstrap
230 * to set %cs, %ds, %es and %ss.
231 */
232 mov %ds, %ax
233 mov %ax, %fs
234 mov %ax, %gs
236 /*
237 * Clear the bss. Not all boot programs do it, and it is our job anyway.
238 *
239 * XXX we don't check that there is memory for our bss and page tables
240 * before using it.
241 *
242 * Note: we must be careful to not overwrite an active gdt or idt. They
243 * inactive from now until we switch to new ones, since we don't load any
244 * more segment registers or permit interrupts until after the switch.
245 */
246 movl $R(end),%ecx
247 movl $R(edata),%edi
248 subl %edi,%ecx
249 xorl %eax,%eax
250 cld
251 rep
252 stosb
根据readelf -a kernel的结果,end的地址是0xc0c06020,由于KERNBASE是
0xc0000000,此处赋给ecx的就是0xc06020。edata的地址是0xc0bab9a0,这实际上就是
.bss段的起始地址,此处赋给edi的就是0xbab9a0。ecx减去edi之后的内容是0x5a680,
这是从.bss段起始地址到end地址之间的字节数。随后将eax清0,作为后续清0操作的
写入值。cld保证edi递增变化。stosb将al的内容写入edi指向的位置。这段代码从
0xc0bab9a0开始连续写入0x5a680个字节的0,从而实现将.bss段清0的目的。
253
254 call recover_bootinfo
255
调用recover_bootinfo获取由loader传入的引导信息。
487 movl 28(%ebp),%ebx /* &bootinfo.version */
488 movl BI_VERSION(%ebx),%eax
489 cmpl $1,%eax /* We only understand version 1 */
490 je 1f
491 movl $1,%eax /* Return status */
492 leave
493 /*
494 * XXX this returns to our caller's caller (as is required) since
495 * we didn't set up a frame and our caller did.
496 */
497 ret
locore.s入口btext的调用格式为(*btext)(howto, bootdev, 0, 0, 0, &bootinfo),在bootinfo
之后压栈的有5个参数,占20个字节,在加上返回地址和在调用recover_bootinfo之前压栈的ebp,
一共有28个字节,因此从当前ebp位置上溯28个字节就是bootinfo结构体的起始地址。上述代码
从bootinfo结构体中取出bi_version字段的内容,判断其是否为1,仅当版本为1时才继续处理。
500 /*
501 * If we have a kernelname copy it in
502 */
503 movl BI_KERNELNAME(%ebx),%esi
504 cmpl $0,%esi
505 je 2f /* No kernelname */
506 movl $MAXPATHLEN,%ecx /* Brute force!!! */
507 movl $R(kernelname),%edi
508 cmpb $'/',(%esi) /* Make sure it starts with a slash */
509 je 1f
510 movb $'/',(%edi)
511 incl %edi
512 decl %ecx
从bootinfo结构体中取出bi_kernelname字段,写入esi。MAXPATHLEN是最大路径长度,定义为1024,
这是kernel中的kernelname数组的尺寸。将该数组相对于KERNBASE偏移地址写入edi,并判断由
bootinfo传入的bi_kernelname是否以"/"开始,若是,则将剩余部分拷入kernelname数组。
519 /*
520 * Determine the size of the boot loader's copy of the bootinfo
521 * struct. This is impossible to do properly because old versions
522 * of the struct don't contain a size field and there are 2 old
523 * versions with the same version number.
524 */
525 movl $BI_ENDCOMMON,%ecx /* prepare for sizeless version */
526 testl $RB_BOOTINFO,8(%ebp) /* bi_size (and bootinfo) valid? */
527 je got_bi_size /* no, sizeless version */
528 movl BI_SIZE(%ebx),%ecx
将bootinfo中肯定会存在的字段的结束位置取出,写入ecx,实际上就是前三个字段,12个字节。
RB_BOOTINFO在/sys/sys/reboot.h中定义为0x80000000,是一个表示是否传入了完整的bootinfo
结构体信息的标志。若传入了完整的bootinfo结构体信息,则将bi_size字段的内容赋给ecx。
531 /*
532 * Copy the common part of the bootinfo struct
533 */
534 movl %ebx,%esi
535 movl $R(bootinfo),%edi
536 cmpl $BOOTINFO_SIZE,%ecx
537 jbe got_common_bi_size
538 movl $BOOTINFO_SIZE,%ecx
539 got_common_bi_size:
540 cld
541 rep
542 movsb
比较传入的bi_size字段的内容与bootinfo结构体的大小是否一致,若传入bi_size小于等于bootinfo
结构体的大小,则以bi_size为准,否则以bootinfo结构体的大小为准。将传入的bootinfo信息拷贝
到bootinfo结构体中。
562 /*
563 * The old style disk boot.
564 * (*btext)(howto, bootdev, cyloffset, esym);
565 * Note that the newer boot code just falls into here to pick
566 * up howto and bootdev, cyloffset and esym are no longer used
567 */
568 olddiskboot:
569 movl 8(%ebp),%eax
570 movl %eax,R(boothowto)
571 movl 12(%ebp),%eax
572 movl %eax,R(bootdev)
573
574 ret
分别从入参中取出相关信息存入boothowto和bootdev变量。返回btext。
256 /* Get onto a stack that we can trust. */
257 /*
258 * XXX this step is delayed in case recover_bootinfo needs to return via
259 * the old stack, but it need not be, since recover_bootinfo actually
260 * returns via the old frame.
261 */
262 movl $R(tmpstk),%esp
tmpstk是在本文件中定义的一块8192字节的连续空间,此处将esp指向这块空间。
实际上,这块空间就紧邻在bootinfo结构体的下面。
291 call identify_cpu
获取cpu识别信息。
292 call create_pagetables
创建第一个页面目录及其页表。
706 /**********************************************************************
707 *
708 * Create the first page directory and its page tables.
709 *
710 */
711
712 create_pagetables:
713
714 /* Find end of kernel image (rounded up to a page boundary). */
715 movl $R(_end),%esi
将kernel的结束地址赋给esi,示例中为0xc06020。
717 /* Include symbols, if any. */
718 movl R(bootinfo+BI_ESYMTAB),%edi
719 testl %edi,%edi
720 je over_symalloc
721 movl %edi,%esi
722 movl $KERNBASE,%edi
723 addl %edi,R(bootinfo+BI_SYMTAB)
724 addl %edi,R(bootinfo+BI_ESYMTAB)
先把bootinfo结构体中的bi_esymtab字段的内容赋给edi,并测试其是否为0,
如果为0,表示传入的bootinfo中没有这个字段。
若非0,则将此内容赋给esi,随后将KERNBASE赋给edi。bootinfo结构体的地址是
0xb00460,bi_symtab和bi_esymtab字段的地址分别是0xb004a0和0xb004a4,这部分结构体
的内容如下:
0xb00460 : 0x00000001 0x00065f24 0x00000000 0x00000000
0xb00470 : 0x0208fe3f 0x004f010f 0x004f010f 0x004f010f
0xb00480 : 0x004f010f 0x004f010f 0x004f010f 0x004f010f
0xb00490 : 0x00000054 0x00008001 0x0000027f 0x0003fbc0
0xb004a0 : 0x00c06020 0x00d06518
723行和724行就是把这两个地址中存放的物理地址加上KERNBASE,得到虚拟地址。
完成添加操作之后同一内存区域的内容如下:
0xb00460 : 0x00000001 0x00065f24 0x00000000 0x00000000
0xb00470 : 0x0208fe3f 0x004f010f 0x004f010f 0x004f010f
0xb00480 : 0x004f010f 0x004f010f 0x004f010f 0x004f010f
0xb00490 : 0x00000054 0x00008001 0x0000027f 0x0003fbc0
0xb004a0 : 0xc0c06020 0xc0d06518
727 /* If we are told where the end of the kernel space is, believe it. */
728 movl R(bootinfo+BI_KERNEND),%edi
729 testl %edi,%edi
730 je no_kernend
731 movl %edi,%esi
732 no_kernend:
733
734 addl $PDRMASK,%esi /* Play conservative for now, and */
735 andl $~PDRMASK,%esi /* ... wrap to next 4M. */
736 movl %esi,R(KERNend) /* save end of kernel */
737 movl %esi,R(physfree) /* next free page is at end of kernel */
将bootinfo结构体中的bi_kernend字段的内容赋给edi,并测试其是否为0,
如果为0,表示传入的bootinfo中没有这个字段。
若非0,则将此内容赋给esi。示例中,bi_kernend字段的值是0xd74000。
PDRMASK的低22比特全1,734和735两行就是把esi中的值,也就是传入的bi_kernend
字段的值向上调整为4M的辈数。示例中即为0x01000000。随后将调整之后的内核结束位置
写入全局变量KERNend和physfree,而下一个空闲页面就是从physfree开始的。
739 /* Allocate Kernel Page Tables */
740 ALLOCPAGES(NKPT)
NKPT目前定义为30,此处即通过ALLOCPAGES分配30个页面。
158 #define ALLOCPAGES(foo) \
159 movl R(physfree), %esi ; \
160 movl $((foo)*PAGE_SIZE), %eax ; \
161 addl %esi, %eax ; \
162 movl %eax, R(physfree) ; \
163 movl %esi, %edi ; \
164 movl $((foo)*PAGE_SIZE),%ecx ; \
165 xorl %eax,%eax ; \
166 cld ; \
167 rep ; \
168 stosb
首先将当前的空闲物理页面地址赋给esi,即0x01000000。PAGE_SIZE是4096,
入参foo是30,由此算出总字节数是122880,将其存入eax,在加上esi中的起始地址,
得到分配区域之后的地址,即0x0101e000,将其存为physfree变量的新值。
随后将edi指向新分配的30个页面空间的起始位置0x01000000。将新分配空间的字节数
0x1e000写入ecx作为计数,165行到168行将这段新分配的空间清0。
741 movl %esi,R(KPTphys)
完成30个页面空间的分配之后,将这部分空间的起始位置,即0x01000000写入全局变量
KPTphys,表示内核页表的物理地址。
749 ALLOCPAGES(NPGPTD)
NPGPTD是页表目录所占用的页面数目,对于非PAE的情况,页表目录共需
4*(2^10)=4096字节,即1个页面。此处再通过ALLOCPAGES分配一个页面的空间,
分配完毕之后,physfree指向新的空闲页面起始地址0x0101f000,esi指向这个页面
的起始位置0x0101e000。
750 movl %esi,R(IdlePTD)
将新分配的这个页面的起始地址0x0101e000写入全局变量IdlePTD,
表示内核页表目录的物理地址。
752 /* Allocate KSTACK */
753 ALLOCPAGES(KSTACK_PAGES)
754 movl %esi,R(p0kpa)
755 addl $KERNBASE, %esi
756 movl %esi, R(proc0kstack)
757
758 ALLOCPAGES(1) /* vm86/bios stack */
759 movl %esi,R(vm86phystk)
760
761 ALLOCPAGES(3) /* pgtable + ext + IOPAGES */
762 movl %esi,R(vm86pa)
763 addl $KERNBASE, %esi
764 movl %esi, R(vm86paddr)
KSTACK_PAGES在/sys/i386/include/param.h中定义为2,此处即再分配2个页面的空间,
physfree指向0x01021000。
分配完成之后,将这段空间的起始地址0x0101f000赋给全局变量p0kpa,
表示proc0的栈的物理地址,另外将这个
起始地址对应的虚拟地址0xc101f000写入全局变量proc0kstack,表示proc0的kstack
空间的(虚拟)地址。
随后再分配一个页面的空间,physfree指向0x01022000。分配完成之后,将这段空间的
起始地址0x01021000赋给全局变量vm86phystk,表示vm86/bios栈的物理地址。
随后再分配三个页面的空间,physfree指向0x01025000。分配完成之后,将这段空间的
起始地址0x01022000赋给全局变量vm86pa,表示vm86区域的物理地址,同时将这个起始
地址对应的虚拟地址0xc1022000赋给全局变量vm86paddr,表示vm86区域的(虚拟)地址。
766 #ifdef SMP
767 /* Allocate cpu0's private data page */
768 ALLOCPAGES(1)
769 movl %esi,R(cpu0pp)
770 addl $KERNBASE, %esi
771 movl %esi, R(cpu0prvpage) /* relocated to KVM space */
772
773 /* Allocate SMP page table page */
774 ALLOCPAGES(1)
775 movl %esi,R(SMPptpa)
776 addl $KERNBASE, %esi
777 movl %esi, R(SMPpt) /* relocated to KVM space */
778 #endif /* SMP */
对于smp的情况,再分配一个页面的空间给cpu0的私有数据页,physfree指向0x01026000。
分配完成之后,将这段空间的起始地址0x01025000赋给全局变量cpu0pp,同时将这个
起始地址对应的虚拟地址0xc1025000赋给全局变量cpu0prvpage。随后再分配一个页面的
空间给SMP页表页,physfree指向0x01027000。分配完成之后,将这段空间的起始地址
0x0x01026000赋给全局变量SMPptpa,同时将这个起始地址对应的虚拟地址0xc1026000
赋给全局变量SMPpt。
780 /*
781 * Enable PSE and PGE.
782 */
783 #ifndef DISABLE_PSE
784 testl $CPUID_PSE, R(cpu_feature)
785 jz 1f
786 movl $PG_PS, R(pseflag)
787 movl %cr4, %eax
788 orl $CR4_PSE, %eax
789 movl %eax, %cr4
790 1:
791 #endif
CPUID_PSE在/sys/i386/include/specialreg.h中定义为0x00000008,这是PSE标志在
cpuid返回值中的位置。全局变量cpu_feature由之前调用的identify_cpu函数设置,
内容获取自cpuid指令。此处判断其PSE对应比特是否为1。PG_PS在/sys/i386/include/pmap.h
中定义为0x080,这是页表目录项和页表项中表示页面尺寸的比特位置。pseflag是在
/sys/i386/i386/pmap.c中定义的全局变量,用于表示是否开启了PSE功能。CR4_PSE在
/sys/i386/include/specialreg.h中定义为0x00000010,这是PSE在cr4寄存器中的比特位置。
上面这几行的意思就是如果cpuid指令的结果显示cpu支持PSE,则将全局变量pseflag设置为
0x80,并将cr4寄存器中PSE对应比特设置为1。
792 #ifndef DISABLE_PG_G
793 testl $CPUID_PGE, R(cpu_feature)
794 jz 2f
795 movl $PG_G, R(pgeflag)
796 movl %cr4, %eax
797 orl $CR4_PGE, %eax
798 movl %eax, %cr4
799 2:
800 #endif
CPUID_PGE在/sys/i386/include/specialreg.h中定义为0x00002000,这是PGE标志在
cpuid返回值中的位置。PG_G在/sys/i386/include/pmap.h中定义为0x100,此处将其赋给
全局变量pgeflag,表示开启了全局页面支持。CR4_PGE在/sys/i386/include/specialreg.h
中定义为0x00000080,这是PGE在cr4寄存器中的比特位置。上面这几行的意思就是如果
cpuid指令的结果显示cpu支持PGE,则将全局变量pgeflag设置为0x100,并将cr4寄存器中
PGE对应比特设置为1。
814 xorl %eax, %eax
815 movl R(KERNend),%ecx
816 shrl $PAGE_SHIFT,%ecx
817 fillkptphys($PG_RW)
清空eax。将KRENend的值0x1000000(16M)写入ecx,并右移12比特,得到页面数目
0x1000(4096)。我们先来看看fillkpt宏的代码:
170 /*
171 * fillkpt
172 * eax = page frame address
173 * ebx = index into page table
174 * ecx = how many pages to map
175 * base = base address of page dir/table
176 * prot = protection bits
177 */
178 #define fillkpt(base, prot) \
179 shll $PTESHIFT,%ebx ; \
180 addl base,%ebx ; \
181 orl $PG_V,%eax ; \
182 orl prot,%eax ; \
183 1: movl %eax,(%ebx) ; \
184 addl $PAGE_SIZE,%eax ; /* increment physical address */ \
185 addl $PTESIZE,%ebx ; /* next pte */ \
186 loop 1b
187
入参ebx是页表内的索引,PTESHIFT在/sys/i386/include/pmap.h中定义为2(非PAE),
179行将ebx左移2位得到的就是页表内的偏移地址。加上base中存放的页表基址得到的
就是页表项的地址。PG_V在/sys/i386/include/pmap.h中定义为0x001,这是页表目录项
或页表项中表示对应的页面当前是否在物理内存中的标志比特。入参eax存放的页面的
基地址,因此低12比特均为0。181和182行将eax的第0比特和第1比特置1,表示对应页面
存在于内存中,属性为读写。183行将在eax中构建好的页表项写入ebx指向的页表项地址。
184行在eax中构造用于描述下一个页面的页表项,185行将ebx指向下一条页表项的地址,
186行循环写入新构建的页表项。
因此,fillkpt的功能就是,给定某个页表页面的基地址(base)、页面保护模式(prot)、
需要填充的第一页表项在页表页面内的索引(ebx)、页面的基地址(eax)、需要填充的
页表项的数目(ecx),填充从ebx开始的ecx个页表项,让其指向从eax开始的ecx个连续
页面,每个页表项的第0比特置1,表示页面存在于物理内存中,第1比特置1,表示属性为读写。
下面来看fillkptphys的代码:
188 /*
189 * fillkptphys(prot)
190 * eax = physical address
191 * ecx = how many pages to map
192 * prot = protection bits
193 */
194 #define fillkptphys(prot) \
195 movl %eax, %ebx ; \
196 shrl $PAGE_SHIFT, %ebx ; \
197 fillkpt(R(KPTphys), prot)
eax为页面物理地址,将其右移12比特之后得到页面在页表内的索引。fillkpt的第一
入参是页表页面的基地址,这里指定的是KPTphys,这是紧邻在KERNend之后的30个
页面空间的起始地址(0x1000000)。 回到814到817行,这几行的意思就是填充
KPTphys(0x1000000)之后的4096个4字节条目,每个条目指向一个页面,这4096个条目
指向的就是从0地址到0x1000000之间的16M空间,这是之前存放内核的地方。
这些页面显然已经存在于物理内存中,因此对应条目的第0比特为1,第1比特为1表示
这些条目的属性为读写。完成设置之后的这4096个页表项的内容如下:
0x1000000 : 0x00000003 0x00001003 0x00002003 0x00003003
0x1000010 : 0x00004003 0x00005003 0x00006003 0x00007003
......
0x1003fe0 : 0x00ff8003 0x00ff9003 0x00ffa003 0x00ffb003
0x1003ff0 : 0x00ffc003 0x00ffd003 0x00ffe003 0x00fff003
826 movl R(IdlePTD), %eax
827 movl $NPGPTD, %ecx
828 fillkptphys($PG_RW)
这几行是建立页表目录所在页面的映射,NPGPTD定义为1,表示页表目录所占页面为1,
IdlePTD是页表目录页面的基地址0x101e000,紧邻在KPTphys指向的30个页面空间的
后面。此处即在前面描述内核16M空间的4096个页表项的后面再用一个页表项来描述
页表目录页面,属性为读写。
0x1000000 : 0x00000003 0x00001003 0x00002003 0x00003003
0x1000010 : 0x00004003 0x00005003 0x00006003 0x00007003
......
0x1003fe0 : 0x00ff8003 0x00ff9003 0x00ffa003 0x00ffb003
0x1003ff0 : 0x00ffc003 0x00ffd003 0x00ffe003 0x00fff003
0x1004000 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004010 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004020 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004030 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004040 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004050 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004060 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004070 : 0x00000000 0x00000000 0x0101e003
中间空的30个条目就对应于KPTphys指向的30个页面。
830 /* Map proc0's KSTACK in the physical way ... */
831 movl R(p0kpa), %eax
832 movl $(KSTACK_PAGES), %ecx
833 fillkptphys($PG_RW)
p0kpa(0x101f000)是紧邻在IdelPTD之后的2个页面的空间,KSTACK_PAGES定义为2,
此处即再分配两个页表项来描述这两个页面,属性为读写。
0x1000000 : 0x00000003 0x00001003 0x00002003 0x00003003
0x1000010 : 0x00004003 0x00005003 0x00006003 0x00007003
......
0x1003fe0 : 0x00ff8003 0x00ff9003 0x00ffa003 0x00ffb003
0x1003ff0 : 0x00ffc003 0x00ffd003 0x00ffe003 0x00fff003
0x1004000 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004010 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004020 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004030 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004040 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004050 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004060 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004070 : 0x00000000 0x00000000 0x0101e003 0x0101f003
0x1004080 : 0x01020003
835 /* Map ISA hole */
836 movl $ISA_HOLE_START, %eax
837 movl $ISA_HOLE_LENGTH>>PAGE_SHIFT, %ecx
838 fillkptphys($PG_RW)
ISA_HOLE_START在/sys/i386/include/pmap.h中定义为0xa0000,ISA_HOLE_LENGTH
定义为0x100000-0xa0000,即393216字节,96个页面。此处即用96个页表条目来映射从
0xa0000开始的96个页面的空间。但这个空间是属于0-KERNend的,之前已经映射过了。
840 /* Map space for the vm86 region */
841 movl R(vm86phystk), %eax
842 movl $4, %ecx
843 fillkptphys($PG_RW)
再分配4个条目来映射vm86phystk(0x1021000)之后的4个页面的空间,属性为读写。
0x1000000 : 0x00000003 0x00001003 0x00002003 0x00003003
0x1000010 : 0x00004003 0x00005003 0x00006003 0x00007003
......
0x1003fe0 : 0x00ff8003 0x00ff9003 0x00ffa003 0x00ffb003
0x1003ff0 : 0x00ffc003 0x00ffd003 0x00ffe003 0x00fff003
0x1004000 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004010 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004020 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004030 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004040 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004050 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004060 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004070 : 0x00000000 0x00000000 0x0101e003 0x0101f003
0x1004080 : 0x01020003 0x01021003 0x01022003 0x01023003
0x1004090 : 0x01024003
845 /* Map page 0 into the vm86 page table */
846 movl $0, %eax
847 movl $0, %ebx
848 movl $1, %ecx
849 fillkpt(R(vm86pa), $PG_RW|PG_U)
这几行将第0页映射到vm86pa指向的区域中,vm86pa指向的是0x1022000区域:
0x1022000 : 0x00000007 0x00000000 0x00000000 0x00000000
只映射了1页,属性为PG_V|PG_RS|PG_U,即0x7,表示当前存在于物理内存中、
可读写、特权级别为用户级。
851 /* ...likewise for the ISA hole */
852 movl $ISA_HOLE_START, %eax
853 movl $ISA_HOLE_START>>PAGE_SHIFT, %ebx
854 movl $ISA_HOLE_LENGTH>>PAGE_SHIFT, %ecx
855 fillkpt(R(vm86pa), $PG_RW|PG_U)
在vm86pa区域中建立ISA hole的映射,共计96个页表条目:
0x1022000 : 0x00000007 0x00000000 0x00000000 0x00000000
0x1022010 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1022270 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x1022280 : 0x000a0007 0x000a1007 0x000a2007 0x000a3007
0x1022290 : 0x000a4007 0x000a5007 0x000a6007 0x000a7007
0x10222a0 : 0x000a8007 0x000a9007 0x000aa007 0x000ab007
0x10222b0 : 0x000ac007 0x000ad007 0x000ae007 0x000af007
0x10222c0 : 0x000b0007 0x000b1007 0x000b2007 0x000b3007
0x10222d0 : 0x000b4007 0x000b5007 0x000b6007 0x000b7007
0x10222e0 : 0x000b8007 0x000b9007 0x000ba007 0x000bb007
0x10222f0 : 0x000bc007 0x000bd007 0x000be007 0x000bf007
0x1022300 : 0x000c0007 0x000c1007 0x000c2007 0x000c3007
0x1022310 : 0x000c4007 0x000c5007 0x000c6007 0x000c7007
0x1022320 : 0x000c8007 0x000c9007 0x000ca007 0x000cb007
0x1022330 : 0x000cc007 0x000cd007 0x000ce007 0x000cf007
0x1022340 : 0x000d0007 0x000d1007 0x000d2007 0x000d3007
0x1022350 : 0x000d4007 0x000d5007 0x000d6007 0x000d7007
0x1022360 : 0x000d8007 0x000d9007 0x000da007 0x000db007
0x1022370 : 0x000dc007 0x000dd007 0x000de007 0x000df007
0x1022380 : 0x000e0007 0x000e1007 0x000e2007 0x000e3007
0x1022390 : 0x000e4007 0x000e5007 0x000e6007 0x000e7007
0x10223a0 : 0x000e8007 0x000e9007 0x000ea007 0x000eb007
0x10223b0 : 0x000ec007 0x000ed007 0x000ee007 0x000ef007
0x10223c0 : 0x000f0007 0x000f1007 0x000f2007 0x000f3007
0x10223d0 : 0x000f4007 0x000f5007 0x000f6007 0x000f7007
0x10223e0 : 0x000f8007 0x000f9007 0x000fa007 0x000fb007
0x10223f0 : 0x000fc007 0x000fd007 0x000fe007 0x000ff007
......
0x1025fe0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1025ff0 : 0x00000000 0x00000000 0x00000000 0x00000000
858 /* Map cpu0's private page into global kmem (4K @ cpu0prvpage) */
859 movl R(cpu0pp), %eax
860 movl $1, %ecx
861 fillkptphys($PG_RW)
继续回到KPTphys(0x1000000)之后的页表区域。再分配一个页表条目给cpu0pp指向的
1个页面的区域(0x1025000),属性为读写。
0x1000000 : 0x00000003 0x00001003 0x00002003 0x00003003
0x1000010 : 0x00004003 0x00005003 0x00006003 0x00007003
......
0x1003fe0 : 0x00ff8003 0x00ff9003 0x00ffa003 0x00ffb003
0x1003ff0 : 0x00ffc003 0x00ffd003 0x00ffe003 0x00fff003
0x1004000 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004010 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004020 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004030 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004040 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004050 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004060 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004070 : 0x00000000 0x00000000 0x0101e003 0x0101f003
0x1004080 : 0x01020003 0x01021003 0x01022003 0x01023003
0x1004090 : 0x01024003 0x01025003
863 /* Map SMP page table page into global kmem FWIW */
864 movl R(SMPptpa), %eax
865 movl $1, %ecx
866 fillkptphys($PG_RW)
再分配一个页表条目给SMPptpa指向的1个页面的区域(0x1026000),属性为读写。
0x1000000 : 0x00000003 0x00001003 0x00002003 0x00003003
0x1000010 : 0x00004003 0x00005003 0x00006003 0x00007003
......
0x1003fe0 : 0x00ff8003 0x00ff9003 0x00ffa003 0x00ffb003
0x1003ff0 : 0x00ffc003 0x00ffd003 0x00ffe003 0x00fff003
0x1004000 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004010 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004020 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004030 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004040 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004050 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004060 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1004070 : 0x00000000 0x00000000 0x0101e003 0x0101f003
0x1004080 : 0x01020003 0x01021003 0x01022003 0x01023003
0x1004090 : 0x01024003 0x01025003 0x01026003
868 /* Map the private page into the SMP page table */
869 movl R(cpu0pp), %eax
870 movl $0, %ebx /* pte offset = 0 */
871 movl $1, %ecx /* one private page coming right up */
872 fillkpt(R(SMPptpa), $PG_RW)
在SMPptpa(0x1026000)页面中分配一个页表条目来映射cpu0pp(0x1025000)指向的页面,
属性为读写。
0x1026000 : 0x01025003 0x00000000 0x00000000 0x00000000
874 /* ... and put the page table table in the pde. */
875 movl R(SMPptpa), %eax
876 movl $MPPTDI, %ebx
877 movl $1, %ecx
878 fillkpt(R(IdlePTD), $PG_RW)
在IdlePTD指向的页表页面目录中创建一条页表目录项来指向SMPptpa指向的SMP页表页面。
MPPTDI定义为1023,表示最后一个页表目录项。属性为读写。我们可以看到,页表目录页面
的其它页表目录项的内容到此时仍然为空。
0x101e000 : 0x00000000 0x00000000 0x00000000 0x00000000
0x101e010 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x101efe0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x101eff0 : 0x00000000 0x00000000 0x00000000 0x01026003
880 /* Fakeup VA for the local apic to allow early traps. */
881 ALLOCPAGES(1)
882 movl %esi, %eax
883 movl $(NPTEPG-1), %ebx /* pte offset = NTEPG-1 */
884 movl $1, %ecx /* one private pt coming right up */
885 fillkpt(R(SMPptpa), $PG_RW)
再分配一个页面,此时physfree指向0x1028000。将SMPptpa(0x1026000)页面中的最后
一个条目用来映射这个新分配的页面,属性为读写。
0x1026000 : 0x01025003 0x00000000 0x00000000 0x00000000
0x1026010 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x1026fe0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x1026ff0 : 0x00000000 0x00000000 0x00000000 0x01027003
888 /*
889 * Create an identity mapping for low physical memory, including the kernel.
890 * The part of this mapping that covers the first 1 MB of physical memory
891 * becomes a permanent part of the kernel's address space. The rest of this
892 * mapping is destroyed in pmap_bootstrap(). Ordinarily, the same page table
893 * pages are shared by the identity mapping and the kernel's native mapping.
894 * However, the permanent identity mapping cannot contain PG_G mappings.
895 * Thus, if the kernel is loaded within the permanent identity mapping, that
896 * page table page must be duplicated and not shared.
897 *
898 * N.B. Due to errata concerning large pages and physical address zero,
899 * a PG_PS mapping is not used.
900 */
901 movl R(KPTphys), %eax
902 xorl %ebx, %ebx
903 movl $NKPT, %ecx
904 fillkpt(R(IdlePTD), $PG_RW)
将IdlePTD(0x101e000)页面中的前NKPT(30)个条目用来映射KPTphys(0x1000000)
指向的30个页面的空间,属性为读写。
0x101e000 : 0x01000003 0x01001003 0x01002003 0x01003003
0x101e010 : 0x01004003 0x01005003 0x01006003 0x01007003
0x101e020 : 0x01008003 0x01009003 0x0100a003 0x0100b003
0x101e030 : 0x0100c003 0x0100d003 0x0100e003 0x0100f003
0x101e040 : 0x01010003 0x01011003 0x01012003 0x01013003
0x101e050 : 0x01014003 0x01015003 0x01016003 0x01017003
0x101e060 : 0x01018003 0x01019003 0x0101a003 0x0101b003
0x101e070 : 0x0101c003 0x0101d003 0x00000000 0x00000000
0x101e080 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x101efe0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x101eff0 : 0x00000000 0x00000000 0x00000000 0x01026003
920 /*
921 * For the non-PSE case, install PDEs for PTs covering the KVA.
922 * For the PSE case, do the same, but clobber the ones corresponding
923 * to the kernel (from btext to KERNend) with 4M (2M for PAE) ('PS')
924 * PDEs immediately after.
925 */
926 movl R(KPTphys), %eax
927 movl $KPTDI, %ebx
928 movl $NKPT, %ecx
929 fillkpt(R(IdlePTD), $PG_RW)
930 cmpl $0,R(pseflag)
931 je done_pde
KPTDI定义为第一个内核页表目录的索引,即768。926到929行将IdlePTD(0x101e000)
页面中的第768项之后的30个页表目录项用来指向KPTphys(0x1000000)指向的30个
页面空间,属性为读写。
0x101e000 : 0x01000003 0x01001003 0x01002003 0x01003003
0x101e010 : 0x01004003 0x01005003 0x01006003 0x01007003
0x101e020 : 0x01008003 0x01009003 0x0100a003 0x0100b003
0x101e030 : 0x0100c003 0x0100d003 0x0100e003 0x0100f003
0x101e040 : 0x01010003 0x01011003 0x01012003 0x01013003
0x101e050 : 0x01014003 0x01015003 0x01016003 0x01017003
0x101e060 : 0x01018003 0x01019003 0x0101a003 0x0101b003
0x101e070 : 0x0101c003 0x0101d003 0x00000000 0x00000000
0x101e080 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x101ebf0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x101ec00 : 0x01000003 0x01001003 0x01002003 0x01003003
0x101ec10 : 0x01004003 0x01005003 0x01006003 0x01007003
0x101ec20 : 0x01008003 0x01009003 0x0100a003 0x0100b003
0x101ec30 : 0x0100c003 0x0100d003 0x0100e003 0x0100f003
0x101ec40 : 0x01010003 0x01011003 0x01012003 0x01013003
0x101ec50 : 0x01014003 0x01015003 0x01016003 0x01017003
0x101ec60 : 0x01018003 0x01019003 0x0101a003 0x0101b003
0x101ec70 : 0x0101c003 0x0101d003 0x00000000 0x00000000
0x101ec80 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x101efe0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x101eff0 : 0x00000000 0x00000000 0x00000000 0x01026003
930 cmpl $0,R(pseflag)
931 je done_pde
若全局变量pseflag为0,则跳转至done_pde,示例中此变量为1。
933 movl R(KERNend), %ecx
934 movl $KERNLOAD, %eax
935 subl %eax, %ecx
936 shrl $PDRSHIFT, %ecx
937 movl $(KPTDI+(KERNLOAD/(1
933行将KERNend(0x1000000)写入ecx。934行将KERNLOAD(0x400000)写入eax。
935行从ecx中减去eax,得到0xc00000。936行将ecx右移22比特,得到3。
937行,(KERNLOAD/(1
946 done_pde:
947 /* install a pde recursively mapping page directory as a page table */
948 movl R(IdlePTD), %eax
949 movl $PTDPTDI, %ebx
950 movl $NPGPTD,%ecx
951 fillkpt(R(IdlePTD), $PG_RW)
将IdlePTD(0x101e000)页面中的第PTDPTDI(767)个页表目录项指向IdelPTD页面,
属性为读写。
0x101e000 : 0x01000003 0x01001003 0x01002003 0x01003003
0x101e010 : 0x01004003 0x01005003 0x01006003 0x01007003
0x101e020 : 0x01008003 0x01009003 0x0100a003 0x0100b003
0x101e030 : 0x0100c003 0x0100d003 0x0100e003 0x0100f003
0x101e040 : 0x01010003 0x01011003 0x01012003 0x01013003
0x101e050 : 0x01014003 0x01015003 0x01016003 0x01017003
0x101e060 : 0x01018003 0x01019003 0x0101a003 0x0101b003
0x101e070 : 0x0101c003 0x0101d003 0x00000000 0x00000000
0x101e080 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x101ebe0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x101ebf0 : 0x00000000 0x00000000 0x00000000 0x0101e003
0x101ec00 : 0x01000003 0x00400083 0x00800083 0x00c00083
0x101ec10 : 0x01004003 0x01005003 0x01006003 0x01007003
0x101ec20 : 0x01008003 0x01009003 0x0100a003 0x0100b003
0x101ec30 : 0x0100c003 0x0100d003 0x0100e003 0x0100f003
0x101ec40 : 0x01010003 0x01011003 0x01012003 0x01013003
0x101ec50 : 0x01014003 0x01015003 0x01016003 0x01017003
0x101ec60 : 0x01018003 0x01019003 0x0101a003 0x0101b003
0x101ec70 : 0x0101c003 0x0101d003 0x00000000 0x00000000
0x101ec80 : 0x00000000 0x00000000 0x00000000 0x00000000
......
0x101efe0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x101eff0 : 0x00000000 0x00000000 0x00000000 0x01026003
960 ret
返回到btex主流程中。
294 /*
295 * If the CPU has support for VME, turn it on.
296 */
297 testl $CPUID_VME, R(cpu_feature)
298 jz 1f
299 movl %cr4, %eax
300 orl $CR4_VME, %eax
301 movl %eax, %cr4
302 1:
如果cpu支持VME,则将cr4寄存器的CR4_VME(0)比特设置为1。
312 movl R(IdlePTD), %eax
313 movl %eax,%cr3 /* load ptd addr into mmu */
将IdlePTD(0x101e000)写入cr3寄存器。
315 movl %cr0,%eax /* get control word */
316 orl $CR0_PE|CR0_PG,%eax /* enable paging */
317 movl %eax,%cr0 /* and let's page NOW! */
318
319 pushl $begin /* jump to high virtualized address */
320 ret
设置cr0寄存器中的CR0_PE(打开保护模式)和CR0_PG(打开调页机制)标志,启动
调页机制。此后程序中使用的就是虚拟地址了。此处通过将begin符号所代表的虚拟
地址压栈再立即返回的形式跳转到虚拟地址begin处(0xc0458a99)。
322 /* now running relocated at KERNBASE where the system is linked to run */
323 begin:
324 /* set up bootstrap stack */
325 movl proc0kstack,%eax /* location of in-kernel stack */
326 /* bootstrap stack end location */
327 leal (KSTACK_PAGES*PAGE_SIZE-PCB_SIZE)(%eax),%esp
328
329 xorl %ebp,%ebp /* mark end of frames */
330
331 #ifdef PAE
332 movl IdlePDPT,%esi
333 #else
334 movl IdlePTD,%esi
335 #endif
336 movl %esi,(KSTACK_PAGES*PAGE_SIZE-PCB_SIZE+PCB_CR3)(%eax)
337
338 pushl physfree /* value of first for init386(first) */
339 call init386 /* wire 386 chip for unix operation */
325行将内核栈起始地址proc0kstack(0xc101f000)写入eax,内核栈共有两个页面,
8192字节,pcb结构体的尺寸是624字节,从这两个页面的顶端去掉624字节之后,
将esp指向向下增长的起始位置(proc0kstack+(8192-624)),即0xc1020d90。
329行将ebp清0。334行将IdlePTD(0x101e000)写入esi。将esi的内容(IdlePTD所表示的
页表目录页面的物理地址)写入内核栈中的pcb结构体中的pcb_cr3字段。
physfree目前指向的是0x01028000,这是之前分配完SMPptpa之后的位置。此处将其压栈,
作为入参first调用init386函数。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/9831/showart_1149550.html |
|