若非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)
