movl $0x10,%eax/*设置数据寄存器,注意CS段寄存器设置为8,而数据寄存器设置为16.仅一位有差距*/
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
call setup_idt
call setup_gdt
movl $0x10,%eax # reload all the segment registers
mov %ax,%ds # after changing gdt. CS was already
mov %ax,%es # reloaded in 'setup_gdt'
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
xorl %eax,%eax
1: incl %eax # check that A20 really IS enabled
movl %eax,0x000000 # loop forever if it isn't
cmpl %eax,0x100000
je 1b
movl %cr0,%eax # check math chip
andl $0x80000011,%eax # Save PG,PE,ET
orl $2,%eax # set MP
movl %eax,%cr0
call check_x87
jmp after_page_tables
check_x87:
和之前一样,代码起始位置设置段寄存器,由于已经定义了GDTR;所以这里只需要设置相应的索引就可以了。注意这里设置的索引刚好是GDTR中的第二项。由于第二项的基地址是0x0,因此以后要读取相应的数据就需要加上偏移,(数据都存放在0x8000以上)。然后开始设置堆栈段和堆栈指针。lss就可以实现。其中stack_start的定义如下:
struct {
long * a;
short b;
} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
其中,堆栈段寄存器同样被设置为0x10,而堆栈指针则是一个在编译时推导出来的值。现在只要知道这个位置设置很安全就可以了,相关的数据会在下一步分析。设置堆栈之后就可以重新设置IDTR和GDTR了,由于在之前的IDTR中并没有实质性的数据。由于GDT变化了,所以相应的需要重新设置段寄存器和堆栈指针。然后验证内存是否回卷,如果回卷,则写到0x000000处的数据将会出现在0x100000处。到了这一步,因为原来有设置CR0寄存器,所以需要将原来的数值保存起来。
check_x87:
fninit
fstsw %ax
cmpb $0,%al
je 1f /* no coprocessor: have to set bits */
movl %cr0,%eax
xorl $6,%eax /* reset MP, set EM */
movl %eax,%cr0
ret
.align 2
1: .byte 0xDB,0xE4 /* fsetpm for 287, ignored by 387 */
ret
然后测试是否存在浮点数处理器,如果没有浮点数处理器则需要设置CR0中的相应的位。
setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea _idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
lidt idt_descr
ret
setup_gdt:
lgdt gdt_descr
ret
.org 0x1000
对IDTR的初始化,也是一个比较复杂的位处理,不过和GDTR表项的设置一样,整个表项比较重要的是段选择器(段基址),然后相对段的偏移以及DPL。整个IDT表项如同上面一样,其中低32位存放在EAX中,而高32位则存放在EDX中。然后,利用循环开始填充IDT表项,最后将IDT表项给存放到IDTR中。注意IDTR中的基址和上限的设置。同样对GDTR的设置也很简单。
.org 0x1000
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000
_tmp_floppy_area:
.fill 1024,1,0
after_page_tables:
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $_main
jmp setup_paging
L6:
jmp L6
int_msg:
.asciz "Unknown interrupt\n\r"
.align 2
ignore_int:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
pushl $int_msg
call _printk
popl %eax
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
after_page_tables中开始,首先进行压栈操作,然后将主函数的地址也存放到堆栈中,因此调用主函数只需要一个ret返回就可以了。
下面的ignore_int函数,首先保存寄存器。然后重新设置段寄存器,这里的设置使得整个代码在内核中进行处理,注意此时代码段已经设置好了。设置之后调用内核的printk函数进行打印输出。
.align 2
setup_paging:
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+7,_pg_dir /* set present bit/user r/w */
movl $pg1+7,_pg_dir+4 /* --------- " " --------- */
movl $pg2+7,_pg_dir+8 /* --------- " " --------- */
movl $pg3+7,_pg_dir+12 /* --------- " " --------- */
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-queue */
.align 2
.word 0
idt_descr:
.word 256*8-1 # idt contains 256 entries
.long _idt
.align 2
.word 0
gdt_descr:
.word 256*8-1 # so does gdt (not that that's any
.long _gdt # magic number, but it works for me :^)
.align 3
_idt: .fill 256,8,0 # idt is uninitialized
_gdt: .quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00c09a0000000fff /* 16Mb */
.quad 0x00c0920000000fff /* 16Mb */
.quad 0x0000000000000000 /* TEMPORARY - don't use */
.fill 252,8,0 /* space for LDT's and TSS's etc */
Linux对页面的处理分为两个部分,第一个是页目录表,第二项才是页表,最后才是偏移。16MB内存下,有4K个页,则需要4K*4个页表项建立地址映射关系,这四个页表存在于页目录表中,在这里存储在内存地址为0的页中,因此需要清除5个页表为0,然后进行设置。在设置页目录后就对页表进行设置,页表中每四个字节存储一个物理地址,这个物理地址和虚拟地址进行映射找到物理地址。不过这里的映射关系都是直接对应的映射关系。也就是高位内存存放在物理的高位内存中。因此在接下来的访问中和没有设置页表是一样的。