linux0.12-6-3(setup.S)

[231页]

6-3 setup.S程序

6-3-1 功能描述

1、利用ROM BIOS中断读取机器系统数据,并将这些数据保存到0x90000开始的位置。
表6-3 setup程序读取并保留的参数。
这些参数将被内核相关程序使用,例如字符设备驱动程序集中的console.c和tty_io.c程序等。
2、 将内核从0x10000移到内存绝对地址0x00.
3、 设置中断描述符表寄存器idtr和全局描述符表寄存器gdtr,开启A20地址线。
4、 重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20~0x2f。
5、 设置CPU的控制寄存器CR0,从而进入32位保护模式运行,并跳转到位于内核模块最前面head.s

说明:
让head.s在32位保护模式下运行,在本程序中临时设置了中断描述符IDT和全局描述符GDT,
并在GDT中设置了当前内核代码段的描述符和数据段的描述符。
下面在head.s程序中会根据内核的需要重新设置这些描述符表。

Linux内核代码中用到的代码段、数据段描述符的格式。
图6-4 程序代码段和数据段的描述符格式。

段描述符存放在描述符表中。
描述符表其实就是内存中描述符项的一个阵列,
它有两类:全局描述符表GDT和局部描述符表LDT。

处理器是通过使用GDTR和LDTR寄存器来定位GDT表和当前的LDT表。
指令lgdt和sgdt用于访问GDTR寄存器;
指令lldt和sldt用于访问LDTR寄存器;

1、 lgdt使用内存中一个6字节操作数来加载GDTR寄存器。
头两个字节代表描述符表的长度,后4个字节是描述符表的基地址。
2、 然而请注意,访问LDTR寄存器的指令lldt所使用的操作数却是一个2B的操作数,
表示全局描述符表GDT中一个描述符项的 选择符。该选择符所对应的GDT表中的描述符项应该对应一个局部描述符表。

举例子:
setup.S程序设置的GDT描述符项,
代码段描述符的值是0x00C09A00000007FF,
表示代码段的限长是8MB(= (0x7ff+1)*4KB),段在线性地址空间中的基地址是0。段类型值0x9A表示该段存在于内存中,
段的特权级别为0,段类型是可读可执行的代码段,段代码是32位的并且段的颗粒是4KB。

数据段描述符的值是0x00C09200000007FF,
表示数据段的限长是8MB,段在线性地址空间中的基地址是0。段类型值0x92表示该段存在于内存中,
段的特权级别为0,段类型是可读可执行的代码段,段代码是32位的并且段的颗粒是4KB。

图6-5 段选择符格式
bit15~bit3 描述符索引
bit2 TI
bit0 bit1 RPL

(a)其中索引值用于选择指定描述符表中8192个描述符中的一个。处理器将
该索引值乘上8,并加上描述符表的基地址即可访问表中指定的段描述符。
(b)表指示器Table Indicator,TI用于指定选择符所引用的描述符表。0-GDT表,1-LDT表
©请求者特权级 Requestor’s Privilege Level,RPL用于保护机制。

注意:GDT表的第一箱(索引值为0)没有被使用。
目的:可以排查异常。段寄存器没有赋值就直接操作。

setup.S程序第215~566行代码复杂,好爱这段代码与内存运行关系不大,因此可以跳过不看。

6-3-2 代码注释

/*
setup.s		(C) 1991 Linus Torvalds
setup.s负责从BIOS中获取系统数据,并将这些数据放到系统内存的适当地方。
此时setup.s和system已经由bootsect引导块加载到内存中。

这段代码询问bios有关内存/磁盘/其他参数,并将这些参数放到一个"安全的"地方:
0x90000-0x901FF,也即原来bootsect代码块曾经在的地方,然后再被缓存块覆盖掉之前
保护模式的system读取。
*/
! 以下这些参数最好和bootsect.s中的相同!
#include <linux/config.h>

INITSEG  = DEF_INITSEG	! we move boot here - out of the way 原来bootsect所处的段。
SYSSEG   = DEF_SYSSEG	! system loaded at 0x10000 (65536). system在0x10000处。
SETUPSEG = DEF_SETUPSEG	! this is the current segment 本程序所在的段地址。

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

entry start
start:

! ok, 整个读磁盘过程都正常,现在将光标位置保存以备今后使用

	mov	ax,#INITSEG	! ds置成INITSEG(0x9000)。这已经在bootsect程序中设置过,但是现在是setup程序,
	mov	ds,ax		!Linus觉得需要再重新设置一下。

! 读取扩展内存的大小值KB
	mov	ah,#0x88
	int	0x15
	mov	[2],ax		!将扩展内存数值存在0x90002处(1个字)。

! 检查显示方式(EGA/VGA)并取参数。
	mov	ah,#0x12
	mov	bl,#0x10
	int	0x10
	mov	[8],ax
	mov	[10],bx
	mov	[12],cx
!检测屏幕当前行列值。若显示卡是VGA卡时则请求用户选择显示行列值,并保存到0x9000E处。	
	mov	ax,#0x5019
	cmp	bl,#0x10
	je	novga
	call	chsvga
novga:	mov	[14],ax
!这段代码使用BIOS中段取屏幕当前光标位置(列、行),并保存在内存0x90000处(2字节)。
!控制台初始化程序会到此处读取该值。
	mov	ah,#0x03	! read cursor pos
	xor	bh,bh
	int	0x10		! save it in known place, con_init fetches
	mov	[0],dx		! it from 0x90000.
	
! 下面这段用于取显示卡当前显示模式;
	mov	ah,#0x0f
	int	0x10
	mov	[4],bx		! bh = display page
	mov	[6],ax		! al = video mode, ah = window width

! 取第一个硬盘的信息(复制硬盘参数表)。
!1个硬盘参数表的首地址竟然是中断向量表0x41,2个硬盘参数表中断向量表0x46,
! 0x90080存放第1个硬盘的表,0x90090存放第2个硬盘的表
	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x41]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0080
	mov	cx,#0x10
	rep
	movsb

! Get hd1 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x46]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	rep
	movsb

! 检查系统是否有第2个硬盘。如果没有则把第2个表清零。
	mov	ax,#0x01500
	mov	dl,#0x81
	int	0x13
	jc	no_disk1
	cmp	ah,#3			!是硬盘吗?类型=3?
	je	is_disk1
no_disk1:
	mov	ax,#INITSEG		!2个硬盘不存在,则对第2个硬盘表清零。
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	mov	ax,#0x00
	rep
	stosb
is_disk1:

! 现在我们要进入保护模式中 ...

	cli			! 从此开始不允许中断。

! 首先我们将内核模块移到正确的位置。
	mov	ax,#0x0000
	cld			! 'direction'=0, movs moves forward
do_move:
	mov	es,ax		! destination segment
	add	ax,#0x1000
	cmp	ax,#0x9000
	jz	end_move
	mov	ds,ax		! source segment
	sub	di,di
	sub	si,si
	mov 	cx,#0x8000
	rep
	movsw
	jmp	do_move

! 此后,我们加载段描述符。
/*
下面指令lidt用于加载中断描述符表IDT寄存器。它的操作数(idt_48)有6字节。前2字节
(字节0-1)是描述符表的字节长度值;后4字节(字节2-5)是描述符表的32位线性基地址,
中断描述符表中的每一个8字节表项指出发送中断时需要调用的代码信息。与中断向量有些像是,但要包含更多的信息。

lgdt指令用于加载全局描述符表(GDT)寄存器,其操作数格式与lidt指令的相同。全局描述符表中
的每个描述符项(8字节)描述了保护模式下数据段和代码段(块)的信息。其中包括段的最大长度
限制(16位)、段的线性地址基址(32位)、段的 特权级、段是否在内存、读写许可权以及其他
一些保护模式运行的标志。
*/
end_move:
	mov	ax,#SETUPSEG	! right, forgot this at first. didn't work :-)
	mov	ds,ax
	lidt	idt_48		! load idt with 0,0 加载IDT寄存器
	lgdt	gdt_48		! load gdt with whatever appropriate  加载GDT寄存器

! 以上的操作很简单,现在我们开启A20地址线。
! 为了能够访问和使用1MB以上的物理内存,我们需要首先开启A20地址线。
	call	empty_8042
	mov	al,#0xD1		! command write
	out	#0x64,al
	call	empty_8042
	mov	al,#0xDF		! A20 on
	out	#0x60,al
	call	empty_8042

/*
linus吐槽IBM把硬件中断放到0x08~0x0f,所以我们就必须重新对8259中断控制器进行编程。
*/
/*
8259芯片主片端口是0x20-0x21,从片端口是0xA0-0xA1.输出值0x11表示初始化命令开始,
它是ICW1命令字,表示边沿触发、多片8259级连、最后要发送ICW4命令字。
*/
	mov	al,#0x11		! initialization sequence
	out	#0x20,al		! send it to 8259A-1
	.word	0x00eb,0x00eb		! jmp $+2, jmp $+2
	out	#0xA0,al		! and to 8259A-2
	.word	0x00eb,0x00eb
	mov	al,#0x20		! start of hardware int's (0x20)
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x28		! start of hardware int's 2 (0x28)
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x04		! 8259-1 is master
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x02		! 8259-2 is slave
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x01		! 8086 mode for both
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0xFF		! mask off all interrupts for now
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al


/*
Linus又吐槽
*/
	mov	ax,#0x0001	! protected mode (PE) bit 保护模式位PE
	lmsw	ax		! This is it! 就这样加载机器状态字!
	jmpi	0,8		! jmp offset 0 of segment 8 (cs) 跳转至cs段偏移0处。
/*
段值8已经是保护模式下的段选择符
段选择符长度为16位(2字节)
8(0b 0000,0000,0000,1000)表示请求特权级0、使用全局描述符表GDT中第2个段描述符想,
该项指出代码的基地址是0
*/


/*
下面这个子程序检查键盘命令队列是否为空。这里不是用超时方法
如果这里死机,则说明PC有问题,我们就没办法再处理下去了。
只有当输入缓冲器为空时(键盘控制器状态寄存器位1=0)才可以对其执行写命令。
*/
empty_8042:
	.word	0x00eb,0x00eb
	in	al,#0x64	! 8042 status port
	test	al,#2		! is input buffer full?
	jnz	empty_8042	! yes - loop
	ret

/*
注意下面215--566行代码牵涉到众多显示卡端口信息,因此比较复杂。
但由于这段代码与内核运行关系不大,因此可以跳过不看。
*/215======================================================
删除了。
	ret	
!566======================================================

[248]

/*
全局描述符表开始处。描述符表由多个8字节长的描述符组成。这里给出了3个描述符想。
第1项无用,但须存在。
第2项是系统代码段描述符;
第3项是系统数据段描述符;
*/
gdt:
	.word	0,0,0,0		! dummy
!在GDT表中这里的偏移量是0x08.它是内核代码段选择符的值
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9A00		! code read/exec
	.word	0x00C0		! granularity=4096, 386
!在GDT表中这里的偏移量是0x08。它是内核数据段选择符的值。  caicai感觉偏移量是问题的应该是0x10
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9200		! data read/write
	.word	0x00C0		! granularity=4096, 386
/*
下面是加载中断描述符表寄存器idtr的指令lidt要求的6字节操作数。前2字节是IDT表的
限长,后4字节是idt表在线性地址空间中的32位基地址。CPU要求在进入保护模式之前需要设置IDT表,
因此这里先设置一个长度为0的空表。
*/
idt_48:
	.word	0			! idt limit=0
	.word	0,0			! idt base=0L
/*
这是加载全局描述符表寄存器gdtr的指令lgdt要求的6字节操作数。前2字节是gdt表的
限长,后4字节是gdt表的线性基地址。这里全局表长度设置为2KB(0x7ff即可),因为
每8字节组成一个段描述符项,所以表中共可有256项。
4字节的线性基地址为0x0009<<16+0x0200+gdt,即0x90200+gdt。
*/

gdt_48:
	.word	0x800		! gdt limit=2048, 256 GDT entries
	.word	512+gdt,0x9	! gdt base = 0X9xxxx

msg1:		.ascii	"Press <RETURN> to see SVGA-modes available or any other key to continue."
		db	0x0d, 0x0a, 0x0a, 0x00
msg2:		.ascii	"Mode:  COLSxROWS:"
		db	0x0d, 0x0a, 0x0a, 0x00
msg3:		.ascii	"Choose mode by pressing the corresponding number."
		db	0x0d, 0x0a, 0x00
!下面是4个显示卡的特征数据串。		
idati:		.ascii	"761295520"
idcandt:	.byte	0xa5
idgenoa:	.byte	0x77, 0x00, 0x66, 0x99
idparadise:	.ascii	"VGA="

!下面是各种显示卡可使用的扩展模式个数和对应的模式号列表。
!厂家:		模式数量:		模式列表:
moati:		.byte	0x02,	0x23, 0x33 
moahead:	.byte	0x05,	0x22, 0x23, 0x24, 0x2f, 0x34
mocandt:	.byte	0x02,	0x60, 0x61
mocirrus:	.byte	0x04,	0x1f, 0x20, 0x22, 0x31
moeverex:	.byte	0x0a,	0x03, 0x04, 0x07, 0x08, 0x0a, 0x0b, 0x16, 0x18, 0x21, 0x40
mogenoa:	.byte	0x0a,	0x58, 0x5a, 0x60, 0x61, 0x62, 0x63, 0x64, 0x72, 0x74, 0x78
moparadise:	.byte	0x02,	0x55, 0x54
motrident:	.byte	0x07,	0x50, 0x51, 0x52, 0x57, 0x58, 0x59, 0x5a
motseng:	.byte	0x05,	0x26, 0x2a, 0x23, 0x24, 0x22
movideo7:	.byte	0x06,	0x40, 0x43, 0x44, 0x41, 0x42, 0x45

!下面是各种牌子VGA显示卡可使用的模式对应的列、行值列表。
!高字节=列数		低字节=行数:
dscati:		.word	0x8419, 0x842c
dscahead:	.word	0x842c, 0x8419, 0x841c, 0xa032, 0x5042
dsccandt:	.word	0x8419, 0x8432
dsccirrus:	.word	0x8419, 0x842c, 0x841e, 0x6425
dsceverex:	.word	0x5022, 0x503c, 0x642b, 0x644b, 0x8419, 0x842c, 0x501e, 0x641b, 0xa040, 0x841e
dscgenoa:	.word	0x5020, 0x642a, 0x8419, 0x841d, 0x8420, 0x842c, 0x843c, 0x503c, 0x5042, 0x644b
dscparadise:	.word	0x8419, 0x842b
dsctrident:	.word 	0x501e, 0x502b, 0x503c, 0x8419, 0x841e, 0x842b, 0x843c
dsctseng:	.word	0x503c, 0x6428, 0x8419, 0x841c, 0x842c
dscvideo7:	.word	0x502b, 0x503c, 0x643c, 0x8419, 0x842c, 0x841c
	
.text
endtext:
.data
enddata:
.bss
endbss:

6-3-3 其他信息

1、 当前内存映像
在setup.s程序执行结束后,内核模块被移动到物理地址0x00,开始处,而从位置0x90000开始处则存放了内核
会使用的一些系统基本参数。
gdt全局表中有3个描述符,第一个是NULL不使用,另外2个分别是代码段描述符和数据段描述符。
它们都指向系统模块的起始处,即物理地址0x00处。
jmp 0,8–8是代表段选择符,0是偏移量为0;

2、 BIOS视频中断0x10
3、 硬盘基本参数表(“INT 0x41”)
中断向量表中,int 0x41的中断向量位置(4X0x41=0x0000:0x0104)存放的并不是中断程序的地址,而是第一个
硬盘的基本参数表。第二个硬盘的基本参数表入口地址存于int 0x46中断向量位置处。

4、 A20地址线问题
IBM公司最初推出个人计算机IBM PC使用的CPU是Intel 8088。该微机中地址线只有20根。在当时内存RAM只有
几百KB或不到1MB时,20根地址线已足够用来寻址这些内存。
IBM公司于1985年引入AT机时,使用的是Intel 80286 CPU,具有24根地址线,最高可寻址16MB,
兼容办法:
(a)通过键盘某个脚判断地址线,setup.S程序(! that was painless, now we enable A20)
(b)使用I/O端口0x92来处理A20信号线
©通过读0xee端口来开启A20信号线,写该端口则会禁止A20信号线。

5、 8259A中断控制器的编程方法
(1)8259A芯片工作原理
(a)主8259A芯片的端口基地址是0x20,从芯片是0xA0。
(b)初始化时要用ICW寄存器,工作时用OCW寄存器随时设置和管理8259A的工作方式。
©A0线用于选择操作的寄存器。在PC/AT微机系统中,当A0线为0时芯片的端口地址是0x20和0xA0(从芯片),
当A0=1时端口就是0x21和0xA1。
(d)产生中断时8259A就会想CPU发送一个INT信号,
而CPU则会在执行完当前的一条指令之后项8259A发送一个INTA来响应中断信号。8259A会将中断数据放到寄存器ISR,同时IRR对应的位被复位。
此后,CPU会向8259A发出第2个INTA脉冲信号,该信号用于通知8259A发送中断号。8259A把中断号的8位数据发送数据总线上给CPU读取。

如果8259A使用的是自动结束中断AEOI方式,那么在第2个INTA脉冲信号的结尾处正在服务器寄存器ISR中的当前服务中断位就会被复位。
否则的话,若8259A处于非自动结束方式,那么在中断服务程序结束时程序就需要向8259A发送一个结束中断EOI命令以服务ISR中的位。
如果中断请求来自级联的第2个8259A芯片,那么就需要想2个芯片都发送EOI命令。

(2)初始化命令字编程
工作之前对每个8259A芯片4个初始化命令字节ICW1~ICW4寄存器写入编程;
工作过程中随时对8259A的3个操作命令字OCW1~OCW3进行编程。

主ICW1=0x11代表中断请求是边沿触发、多片8259A级联并且最后需要发送ICW4
mov al,#0x11 ! initialization sequence
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb ! jmp $+2, jmp $+2
out #0xA0,al ! and to 8259A-2 从ICW1=0x11
.word 0x00eb,0x00eb
主ICW2=0x20代表主片中断请求0级7级对应中断范围0x200x27
mov al,#0x20 ! start of hardware int’s (0x20)
out #0x21,al
.word 0x00eb,0x00eb
从ICW2=0x28代表从片中断请求8级15级对应中断范围0x280x2f
mov al,#0x28 ! start of hardware int’s 2 (0x28)
out #0xA1,al
.word 0x00eb,0x00eb
主片的ICW3设置为0x04,即S2=1,其余各位为0,。表示主芯片的IR2引脚连接一个从芯片。
mov al,#0x04 ! 8259-1 is master
out #0x21,al
.word 0x00eb,0x00eb
从芯片的ICW3被设置为0x02,即其标号为2。表示从片连接到主片的IR2引脚。
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
.word 0x00eb,0x00eb

主从芯片ICW4命令字的值均为0x01.表示8259A芯片被设置成普通全嵌套、非缓冲、非自动结束中断方式,并且用于8086及其兼容系统。
mov al,#0x01 ! 8086 mode for both
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
.word 0x00eb,0x00eb

主从OCW1用于对8259A中断屏蔽寄存器IMR进行读/写操作。
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al

ICW1 D4=1且A0=0时,表示对ICW1编程。
ICW2 A0=1时表示对ICW2进行设置。
ICW3 A0=1时表示对ICW3进行设置。
ICW4 A0=1时表示对ICW4进行设置。
OCW1用于对8259A中中断屏蔽寄存器IMR进行读/写操作。地址线A0需为1。
0CW2用于发送EOI命令或设置中断优先级的自动循环方式。当位D4D3=00,地址线A0=0时表示对OCW2进行编程设置。
0CW3用于设置特殊屏蔽方式和读取寄存器状态(IRR和ISR)。当D4D3=01、地址线A0=0时表示对OCW3进行编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值