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 iretafter_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,然后进行设置。在设置页目录后就对页表进行设置,页表中每四个字节存储一个物理地址,这个物理地址和虚拟地址进行映射找到物理地址。不过这里的映射关系都是直接对应的映射关系。也就是高位内存存放在物理的高位内存中。因此在接下来的访问中和没有设置页表是一样的。