setup.S之保护模式

从实模式切换到保护模式经历下面过程:

  1. cmpw $0, %cs:realmode_swtch;首先有没有切换到保护模式的例程(subroutine);如果有则跳转到这个例程处,在我们调试环境中没有。
	cmpw	$0, %cs:realmode_swtch
	jz	rmodeswtch_normal //跳转rmodeswtch_normal
  1. 跳转到default_switch处,通过outb %al, $0x70向IO 0x70端口处,写入0x80值,来屏蔽NMI。
default_switch:
	cli					# no interrupts allowed !   0x90c56
	movb	$0x80, %al			# disable NMI for bootup
						# sequence
	outb	%al, $0x70
	lret
  1. 设置code32的开始地址,也就是跳转到head.S处的地址(0x10 0000)
rmodeswtch_end:
# we get the code32 start address and modify the below 'jmpi'
# (loader may have changed it)
	movl	%cs:code32_start, %eax            
	movl	%eax, %cs:code32                  

code32_start:				# here loaders can put a different
					# start address for 32-bit code.
#ifndef __BIG_KERNEL__
		.long	0x1000		#   0x1000 = default for zImage
#else
		.long	0x100000	# 0x100000 = default for big kernel
#endif
  1. 通过判断loadflags和type_of_loader值来判断是否需要搬移保护模式代码(head.S)到0x100000(bzImage)处;grub已经把内核保护代码搬移到0x100000处,直接跳转到end_move_self
	testb	$LOADED_HIGH, %cs:loadflags            //0x81  0x90ac3
	jz	do_move0			# .. then we have a normal low //0x90ac9
						# loaded zImage
						# .. or else we have a high
						# loaded bzImage
	jmp	end_move			# ... and we skip moving     //0x90acb

end_move:
# then we load the segment descriptors
	movw	%cs, %ax			# aka SETUPSEG //0x90af2
	movw	%ax, %ds            //0x90af4
		
# Check whether we need to be downward compatible with version <=201
	cmpl	$0, cmd_line_ptr  //0x99000  0x90af4
	jne	end_move_self		# loader uses version >=202 features  //0x90afc
	cmpb	$0x20, type_of_loader
	je	end_move_self		# bootsect loader, we know of it
  1. 判断A20是否打开,如果没有打开,这尝试着上节说的三种方式打开,在我们的环境中已经打开。
  2. 加载中断向量寄存器(idt)和全局描述表寄存器(gdtr)。
# set up gdt and idt
	lidt	idt_48				# load idt with 0,0
	xorl	%eax, %eax			# Compute gdt_base
	movw	%ds, %ax			# (Convert %ds:gdt to a linear ptr)
	shll	$4, %eax
	addl	$gdt, %eax
	movl	%eax, (gdt_48+2)
	lgdt	gdt_48				# load gdt with whatever is
gdt:
	.fill GDT_ENTRY_BOOT_CS,8,0

	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9A00				# code read/exec
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)

	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9200				# data read/write
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)
gdt_end:
	.align	4
	
	.word	0				# alignment byte

gdt_48:
	.word	gdt_end - gdt - 1		# gdt limit
	.word	0, 0				# gdt base (filled in later)

把gdt的标签的地址给gdt_48高32位,低16位是GDT的大小。
7. 重启协处理器

# make sure any possible coprocessor is properly reset..
	xorw	%ax, %ax
	outb	%al, $0xf0
	call	delay

	outb	%al, $0xf1
	call	delay
  1. 屏蔽所有中断
# well, that went ok, I hope. Now we mask all interrupts - the rest
# is done in init_IRQ().
	movb	$0xFF, %al			# mask all interrupts for now
	outb	%al, $0xA1
	call	delay
	
	movb	$0xFB, %al			# mask all irq's but irq2 which
	outb	%al, $0x21			# is cascaded
  1. 设置CR0的PE(Protedted Enable)
	movw	$1, %ax				# protected mode (PE) bit
	lmsw	%ax				# This is it!
  1. 设置esi为32位指针
flush_instr:
	xorw	%bx, %bx			# Flag to indicate a boot
	xorl	%esi, %esi			# Pointer to real-mode code
	movw	%cs, %si
	subw	$DELTA_INITSEG, %si   //si=0x9020-x020=0x9000
	shll	$4, %esi			# Convert to 32-bit pointer  esi=0x90000
  1. 跳转到0x100000处,也就是compressed/head.S处
# jump to startup_32 in arch/i386/boot/compressed/head.S
#	
# NOTE: For high loaded big kernels we need a
#	jmpi    0x100000,__BOOT_CS
#
#	but we yet haven't reloaded the CS register, so the default size 
#	of the target offset still is 16 bit.
#       However, using an operand prefix (0x66), the CPU will properly
#	take our 48 bit far pointer. (INTeL 80386 Programmer's Reference
#	Manual, Mixing 16-bit and 32-bit code, page 16-6)

	.byte 0x66, 0xea			# prefix + jmpi-opcode
     code32:	.long	0x1000				# will be set to 0x100000
						# for big kernels
	.word	__BOOT_CS                           //0x10

.byte 0x66,0xea 0x66是机器码的前缀,告诉CPU取48位,也就是6个字节.也就是ljmp 16bit:32bit
CS=code32[15:0]=_BOOT_CS=0x10 EIP=code32[47:16]=0x10000,这样就成功跳转到0x10000处,也就是compressed/head.S处。
说明:
CPU切换保护模式和实模式,可以通过处置位CR0寄存器的PE位。
PE位为1---------->CPU工作在保护模式下
PE位为0---------->CPU工作在实模式下

段寄存器和段描述符寄存器

段描述符寄存器(GDTR)是48位寄存器,结构如下

32 bits(the location of the GDT in memory) 16 bits (size of GDT)

段选择寄存器(CS,SS,DS,ES)是16位寄存器,结构如下

索引号13位 TI(1位) RPL(2位)

全局段描述符

整个系统中,全局描述符表GDT只有一张(一个处理器对应一个GDT),GDT可以被放在内存的任何位置。可以把GDT的入口地址存放在GDTR中。这样CPU就可以通过GDTR去访问GDT。
我们将GDT设定到某个内存位置后,通过LGDT指令将GDT的入口地址装入此寄存器。
LGDT的使用如下:

LGDT src
IDTR(Limit)  SRC[0:15];
IDTR(Base)  SRC[16:47] AND 00FFFFFFH; 

全局描述符的某项的结构如下:
全局描述符的结构
比如在Linux中的head.S中这样初始化全局描述符:

ENTRY(cpu_gdt_table)
	.quad 0x0000000000000000	/* NULL descriptor */
	.quad 0x0000000000000000	/* 0x0b reserved */
	.quad 0x0000000000000000	/* 0x13 reserved */
	.quad 0x0000000000000000	/* 0x1b reserved */
	.quad 0x0000000000000000	/* 0x20 unused */
	.quad 0x0000000000000000	/* 0x28 unused */
	.quad 0x0000000000000000	/* 0x33 TLS entry 1 */
	.quad 0x0000000000000000	/* 0x3b TLS entry 2 */
	.quad 0x0000000000000000	/* 0x43 TLS entry 3 */
	.quad 0x0000000000000000	/* 0x4b reserved */
	.quad 0x0000000000000000	/* 0x53 reserved */
	.quad 0x0000000000000000	/* 0x5b reserved */

	.quad 0x00cf9a000000ffff	/* 0x60 kernel 4GB code at 0x00000000 */
	.quad 0x00cf92000000ffff	/* 0x68 kernel 4GB data at 0x00000000 */
	.quad 0x00cffa000000ffff	/* 0x73 user 4GB code at 0x00000000 */
	.quad 0x00cff2000000ffff	/* 0x7b user 4GB data at 0x00000000 */

bit40~bit46,对应于描述项中的type以及S标志和DPL位段。
KERNEL_CS:DPL=0,表示0级:S位为1,表示代码段或者数据段:type为1010,表示代码段,可读,可执行,尚未收到访问。
KERNEL_CS:DPL=0,表示0级:S位为1,表示代码段或者数据段:type为0010,表示代码段,可读,尚未收到访问。

比如cpu_gdt_table在内存中的位置是0x1000,则gdtr是0x1000 00。

#define GDT_ENTRY_DEFAULT_USER_CS	14
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)

#define GDT_ENTRY_DEFAULT_USER_DS	15
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)

逻辑地址转换成线性地址的过程

例如给出逻辑地址:比如在代码CS=0x21
21h:12345678h转换为线性地址。
a. 选择子SEL=21h=0000000000100 0 01b 他代表的意思是:选择子的index=4即100b选择GDT中的第4个描述符;TI=0代表选择子是在GDT选择;左后的01b代表特权级RPL=1

b. OFFSET=12345678h若此时GDT第四个描述符中描述的段基址(Base)为11111111h,则线性地址=11111111h+12345678h=23456789h

问题:gdtr存放的地址是物理地址吗?是不是CPU直接从gdtr拿到的地址是物理地址,直接找到gdt,然后根据偏移量,找到相应的段描述符;而不是还要进行一层转换?
答:lgdt gdt_48加载的是物理地址.。

控制寄存器(CR0,CR1,CR2,CR3,CR4)

控制寄存器可以控制CPU的一些特性。
CR0寄存器说明:
CR0位是保护允许位PE(Protedted Enable),用于启动保护模式,如果PE位置1,则保护模式启动,如果PE=0,则在实模式下运行。
CR0的第1 位是监控协处理位MP(Moniter coprocessor),它与第3位一起决定:当TS=1时操作码WAIT是否产生一个“协处理器不能使用”的出错信号。第3位是任务转换位(Task Switch),当一个任务转换完成之后,自动将它置1。随着TS=1,就不能使用协处理器。
CR0的第2位是模拟协处理器位 EM (Emulate coprocessor),如果EM=1,则不能使用协处理器,如果EM=0,则允许使用协处理器。
CR0的第4位是微处理器的扩展类型位 ET(Processor Extension Type),其内保存着处理器扩展类型的信息,如果ET=0,则标识系统使用的是287协处理器,如果 ET=1,则表示系统使用的是387浮点协处理器。
CR0的第31位是分页允许位(Paging Enable),它表示芯片上的分页部件是否允许工作。
CR0的第16位是写保护未即WP位(486系列之后),只要将这一位置0就可以禁用写保护,置1则可将其恢复。

CR1是未定义的控制寄存器,供将来的处理器使用。
CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址。
CR3是页目录基址寄存器,保存页目录表的物理地址,页目录表总是放在以4K字节为单位的存储器边界上,因此,它的地址的低12位总为0,不起作用,即使写上内容,也不会被理会。
CR4在Pentium系列(包括486的后期版本)处理器中才实现,它处理的事务包括诸如何时启用虚拟8086模式等。

LMSW r16/m16: 将操作数装入CR0的0-15位。

ljmp

ljmp $0xfebc, $0x12345678
Long jump, use 0xfebc for the CS register and 0x12345678 for the EIP register

realmode_swtch:

A 16-bit real mode far subroutine invoked immediately before entering protected mode. 
 The default routine disables NMI, so your routine should probably do so, too.

猜你喜欢

转载自blog.csdn.net/chengbeng1745/article/details/82971693