第二章 引导和初始化.docx
- 文档编号:17141419
- 上传时间:2023-07-22
- 格式:DOCX
- 页数:88
- 大小:65.94KB
第二章 引导和初始化.docx
《第二章 引导和初始化.docx》由会员分享,可在线阅读,更多相关《第二章 引导和初始化.docx(88页珍藏版)》请在冰点文库上搜索。
第二章引导和初始化
第二章引导和初始化
2.0AT&T汇编语言格式
Linux系统所采用的汇编语言遵循AT&T汇编语言语法,该语法与Intel指令格式和Microsoft汇编语言语法稍有不同:
1、指令操作数的顺序是先源后目的,与Intel指令的先目的后源的顺序相反;
2、寄存器操作数前加前缀%;
3、立即数前加前缀$;
4、操作码加后缀以指明操作数的长度,这些后缀有b(8位)、w(16位)、l(32位);
例:
AT&TIntel
moww%bx,%ax//movax,bx
xorl%eax,%eax//xoreax,eax
movw$1,%ax//movax,1
movbX,%ah//movah,byteptrX
movwX,%ax//movax,wordptrX
movlX,%eax//moveax,X
5、大部分指令的操作码都与Intel指令相同,只有下面几个是例外:
movsSD//movsx,短到长,高位补符号位
movzSD//movzx,短到长,高位补0
这里,S是代表源操作数长度的后缀,D是代表目的操作数长度的后缀。
如:
movswl%ax,%ecx//movsxecx,ax
cbtw//cbw,将AL中的一个字节扩充为一个字(AX),
//AH是AL的符号位
cwtl//cwde,将AX中的一个字扩充为一个双字(EAX),
//EAX的高位是AX的符号位
cwtd//cwd,将AX中的一个字扩充为两个字(DX:
AX)
//DX是AX的符号位
cltd//cdq,将EAX中的一个双字扩充为两个双字
//(EDX:
EAX),EDX是EAX的符号位
lcall$S,$O//callfarS:
O
ljmp$S,$O//jmpfarS:
O
lret$V//retfarV
6、操作码的前缀(如rep)要单独写一行,不要和它修饰的指令(如movsb、stosb)写在同一行上;
7、内存间接寻址的格式不同
AT&TIntel
disp(base,index,scale)[base+index*scale+disp]
例:
movl4(%ebp),%eax//moveax,[ebp+4]
addl(%eax,%eax,4),%ecx//addecx,[eax+eax*4]
movb$4,%fs:
(%eax)//movfs:
eax,4
movl_array(,%eax,4),%eax//moveax,[4*eax+array]
movw_array(%ebx,%eax,4),%cx//movcx,[ebx+4*eax+array])
8、局部标号可以用数字,而且可以重复。
在以这些标号为目的的转移指令上,标号要带上后缀,b表示向前,f表示向后。
例:
orw%bx,%bx
jz1f
1:
movl$0x101000,%eax
movl%eax,%cr3/*setthepagetablepointer..*/
movl%cr0,%eax
orl$0x80000000,%eax
movl%eax,%cr0/*..andsetpaging(PG)bit*/
jmp1f/*flushtheprefetch-queue*/
1:
movl$1f,%eax
jmp*%eax/*makesureeipisrelocated*/
1:
9、实模式下的语法与Intel指令语法基本相同;
10、内嵌汇编
可以用上述格式的汇编单独写程序(有许多宏定义和它特有的文件格式),而后用gcc将其汇编成目标代码。
也可以将一段汇编程序嵌入在C程序中。
嵌入汇编程序的格式如下:
__asm____volatile__(
asmstatements
:
outputs
:
inputs
:
registers-modified
);
其中:
●asmstatements是一组AT&T格式的汇编语言语句,每个语句一行,由\n分隔各行。
所有的语句括在一对双引号内。
其中使用的寄存器前面要加两个%%做前缀;转移指令多是局部转移,因此多使用数字标号。
●inputs指明程序的输入参数,每个输入参数都括在一对圆括号内,各参数用逗号分开。
每个参数前加一个用双引号括起来的标志,告诉编译器把该参数装入到何处。
可用的标志有:
“g”:
让编译器决定如何装入它;“a”:
装入到ax/eax;“b”:
装入到bx/ebx;“c”:
装入到cx/ecx;“d”:
装入到dx/edx;“D”:
装入到di/edi;“S”:
装入到si/esi;“q”:
a、b、c、d寄存器等;“r”:
任一通用寄存器;“i”:
整立即数;“p”:
有效内存地址;“=”:
输出;“+”:
既是输入又是输出;“&”:
改变其值之前写;“%”:
与下一个操作数见可互换;“#”:
忽略其后的字符,直到逗号;“*”:
当优先选择寄存器时,忽略下面的字符;“0~9”:
指定一个操作数,它既做输入又做输出。
通常用“g”。
●outputs指明程序的输出位置,通常是变量。
每个输出变量都括在一对圆括号内,各个输出变量间用逗号隔开。
每个输出变量前加一个标志,告诉编译器从何处输出。
可用的标志与输入参数用的标志相同,只是前面加“=”。
如“=g”。
输出操作数必须是左值,而且必须是只写的。
如果一个操作数即做输出又做输入,那么必须将它们分开:
一个只写操作数,一个输入操作数。
输入操作数前加一个数字限制(0~9),指出输出操作数的序号,告诉编译器它们必须在同一个物理位置。
两个操作数可以是同一个表达式,也可以是不同的表达式。
●registers-modified告诉编译器程序中将要修改的寄存器。
每个寄存器都用双引号括起来,并用逗号隔开。
如“ax”。
如果汇编程序中引用了某个特定的硬件寄存器,就应该在此处列出这些寄存器,以告诉编译器这些寄存器的值被改变了。
如果汇编程序中用某种不可预测的方式修改了内存,应该在此处加上“memory”。
这样以来,在整个汇编程序中,编译器就不会把它的值缓存在寄存器中了。
●__volatile__是可选的,它防止编译器修改该段汇编语句(重排序、重组、删除等)。
●输入参数和输出变量按顺序编号,先输出后输入,编号从0开始。
程序中用编号代表输入参数和输出变量(加%做前缀)。
●输入、输出、寄存器部分都可有可无。
如有,顺序不能变;如无,应保留“:
”,除非不引起二意性。
例:
inti=0,j=1,k=0;
__asm____volatile__("
pushl%%eax\n
movl%1,%%eax\n
addl%2,%%eax\n
movl%%eax,%0\n
popl%%eax"
:
"=g"(k)
:
"g"(i),"g"(j)
:
"ax","memory"
);//k=i+j
2.1开机过程
当机器加电或Reset时,处理器要完成一系列内建的自检和硬件初始化动作。
处理器初始化时,要为它的各个寄存器设置初值,并将其操作模式设置为实模式。
初始化后,各寄存器的状态如下:
Table2-1.32-BitIntelArchitectureProcessorStates
FollowingPower-up,Reset,orINIT
Register
P6FamilyProcessors
PentiumProcessor
Intel486?
Processor
EFLAGS
00000002H
00000002H
00000002H
EIP
0000FFF0H
0000FFF0H
0000FFF0H
CR0
60000010H
60000010H2
60000010H2
CR2,CR3,CR4
00000000H
00000000H
00000000H
CS
Selector=F000H
Base=FFFF0000H
Limit=FFFFH
AR=Present,R/W,
Accessed
Selector=F000H
Base=FFFF0000H
Limit=FFFFH
AR=Present,R/W,
Accessed
Selector=F000H
Base=FFFF0000H
Limit=FFFFH
AR=Present,R/W,
Accessed
SS,DS,ES,FS,GS
Selector=0000H
Base=00000000H
Limit=FFFFH
AR=Present,R/W,
Accessed
Selector=0000H
Base=00000000H
Limit=FFFFH
AR=Present,R/W,
Accessed
Selector=0000H
Base=00000000H
Limit=FFFFH
AR=Present,R/W,
Accessed
EDX
000006xxH
000005xxH
000004xxH
EAX
0
0
0
EBX,ECX,ESI,EDI,EBP,ESP
00000000H
00000000H
00000000H
GDTR,IDTR
Base=00000000H
Limit=FFFFH
AR=Present,R/W
Base=00000000H
Limit=FFFFH
AR=Present,R/W
Base=00000000H
Limit=FFFFH
AR=Present,R/W
LDTR,Task
Register
Selector=0000H
Base=00000000H
Limit=FFFFH
AR=Present,R/W
Selector=0000H
Base=00000000H
Limit=FFFFH
AR=Present,R/W
Selector=0000H
Base=00000000H
Limit=FFFFH
AR=Present,R/W
DR0,DR1,DR2,DR3
00000000H
00000000H
00000000H
DR6
FFFF0FF0H
FFFF0FF0H
FFFF1FF0H
DR7
00000400H
00000400H
00000000H
Time-Stamp
Counter
PoweruporReset:
0H
INIT:
Unchanged
PoweruporReset:
0H
INIT:
Unchanged
NotImplemented
DataandCodeCache,TLBs
Invalid
Invalid
Invalid
由此可见:
控制寄存器CR0的值为60000010H,所以处理器处于实模式,而且分页机制没有启动(PE=0,PG=0);
代码段寄存器CS的初值为F000H,所以处理器的特权级为0,其对应的代码段的基地址为FFFF0000H,界限为FFFFH(64K大小),并且该代码段在内存,允许读写,缺省的地址和操作数长度为16位;
指令寄存器EIP的初值为0000FFF0H。
所以,开机的过程如下:
●机器加电,处理器完成自检和初始化,设置各寄存器的初值,而后开始执行指令。
●处理器执行的第一条指令的地址是:
FFFF0H,该地址离实模式下处理器的最大可编地址FFFFFH仅16个字节。
该处是驻留在ROM中的BIOS的入口地址,处理器从此处开始执行指令。
因此最先获得机器控制权的是BIOS。
●BIOS完成对整个机器系统的检测,并将有关系统配置的基本信息记录在内存的BIOS数据区,然后,从引导盘(软盘、硬盘、光驱等)上将一个引导扇区(如软盘的0道0扇区)读入到内存的7C00H处,最后转到7C00H,将对机器的控制权交给引导程序。
●引导程序将操作系统内核读入内存,再把对机器的控制权交给操作系统内核。
●操作系统内核完成必要的初始化设置,创建必要的数据结构,装入并启动必要的程序后,整个机器就进入了正常的执行状态。
其后,操作系统一直控制机器,直到关机。
在操作系统内核接管机器控制权后,它要做许多系统初始化工作,其中最主要的一个工作是将处理器由实模式切换到保护模式。
因为在保护模式下运行需要很多数据结构,如GDT、IDT、页目录、页表等。
这些数据结构必须在切换前后建好。
真正的切换非常简单,用MOV指令向控制寄存器CR0中写入一个数,将它的PE标志置1即可。
切换过程如下:
1、关掉中断。
CLI指令可以关掉可屏蔽硬件中断,不可屏蔽中断(NMI)可以通过外部电路关掉。
总之要保证在模式切换期间不会发生中断和异常。
2、执行LGDT指令将GDT的基址和界限信息装入GDTR寄存器。
当然在此之前要先建好GDT表。
3、执行MOVCR0指令,设置控制寄存器CR0的PE位,也可以同时设置其PG位。
4、紧接着MOVCR0指令的应是一条FARJMP指令或FARCALL指令,通常是JMP到下一条指令。
5、如果准备用局部描述符表,执行LLDT指令,将LDT段描述符的信息装入到LDTR寄存器中。
6、执行LTR指令,将第一个保护模式任务的段选择符和有关信息装入到任务寄存器TR中,也可以仅指定一个可写的内存区用于在任务切换时保存TSS信息。
7、第4步的JMP或CALL指令已经重新设置了CS寄存器,其余段寄存器的值仍然是其在实模式时的值。
重装段寄存器DS、SS、ES、FS和GS,执行到新任务的JMP或CALL指令,这两条指令将重设段寄存器的值,并转到新的代码段。
8、执行LIDT指令,将保护模式的IDT的基址和界限等信息装入到IDTR寄存器中。
当然,在此之前要先建立好IDT表。
9、开中断。
STI指令可以打开可屏蔽硬中断,做必要的硬件操作打开NMI中断。
注:
第3、4步之间如有指令,将可能产生不可预料的错误。
以下以软盘启动为例讨论Linux的启动过程,从硬盘启动与软盘启动稍有不同,可参见LILO引导说明。
2.2内核的组织
真正的Linux内核是被打包压缩的可执行代码,因此在它的前面必须附加一段没有压缩的解压缩代码。
利用该段解压缩代码展开压缩的Linux内核映象。
但这两部分又必须由引导程序将其装入内存。
引导程序分两部分:
占一个扇区的引导程序(Bootsect)和仅随其后的设置程序(Setup)。
所以一个引导盘最前面若干扇区的组织如下:
Bootsect
Setup
解压缩(Head+Inflate)
Linux内核
由于引导时还没有文件系统,所以,这些文件的装入是按扇区而不是按文件进行的,因此其在引导盘上的位置不能变,否则将无法引导系统。
2.3引导扇区Bootsect
BIOS将软盘的第一个扇区,512个字节,读到内存的7C00处,而后将控制转到7C00。
第一个扇区的内容是Bootsect,它从BIOS接到对机器的控制权,并开始执行。
Bootsect做如下工作:
1、将自己移到0x90000处。
movax,#BOOTSEG/*0x07C0*/
movds,ax
movax,#INITSEG/*0x9000*/
moves,ax
movcx,#256/*256个字,即512个字节*/
subsi,si
subdi,di
cld
rep
movsw
jmpigo,INITSEG/*转到段0x9000处的go位置执行*/
2、在0x94000–12处设栈。
go:
movdi,#0x4000-12
movds,ax
movss,ax!
putstackatINITSEG:
0x4000-12.
movsp,di
栈向下增长。
从90200到94000有足够的空间(31个扇区),可以放下Setup程序(4到10个扇区)和栈。
空下的12个字节保存磁盘参数。
3、读入Setup
将Setup程序读入到90200处,即紧接着Bootsect。
通过BIOS中断13H一次将其全部读入。
load_setup:
xorah,ah!
resetFDC
xordl,dl
int0x13
xordx,dx!
drive0,head0
movcl,#0x02!
sector2,track0
movbx,#0x0200!
address=512,inINITSEG
movah,#0x02!
service2,nrofsectors
moval,setup_sects!
扇区数(assumeallonhead0,track0)
int0x13!
readit
jncok_load_setup!
ok-continue
pushax!
dumperrorcode
callprint_nl!
CR+LF
movbp,sp
callprint_hex!
printthenumberinds:
sp,thatisax
popax
jmpload_setup
ok_load_setup:
4、读入操作系统内核
将操作系统内核读入到10000处。
内核大小不超过508K(7F000),因此,读入后内核在100000到8F000,离引导程序(Bootsect,90000)还有4K的距离。
因为内核较大(大于400K),它在磁盘上占多个磁道,在内存中占多个数据段。
为加快装入速度,最好是每次读一个磁道。
为此要对磁盘做一些测试,看一次能读入多少扇区。
同时还要注意当段满时,调整段寄存器的值,以避免越界。
内核在磁盘上的开始位置紧接着Setup程序,因此从上次装入的结束位置开始读即可。
sread:
.word0!
sectorsreadofcurrenttrack
head:
.word0!
currenthead
track:
.word0!
currenttrack
read_it:
moval,setup_sects
incal
movsread,al!
now,wehaveread5sectors:
1bootsect+4setupsectors
movax,es
testax,#0x0fff
die:
jnedie!
esmustbeat64kBboundary
xorbx,bx!
bxisstartingaddresswithinsegment
rp_read:
movax,es
subax,#SYSSEG
cmpax,syssize!
haveweloadedallyet?
jbeok1_read
ret
ok1_read:
movax,sectors
subax,sread
movcx,ax
shlcx,#9
addcx,bx
jncok2_read
jeok2_read
xorax,ax
subax,bx
shrax,#9!
readaxsectorscanfillupthecurrentexsegment
ok2_read:
callread_track
movcx,ax!
thesectorsreadbyeread_track
addax,sread
cmpax,sectors
jneok3_read
movax,#1
subax,head
jneok4_read
inctrack
ok4_read:
movhead,ax
xorax,ax
ok3_read:
movsread,ax
shlcx,#9
addbx,cx
jncrp_read
movax,es
addah,#0x10
moves,ax
xorbx,bx
jmprp_read
read_track:
!
readatrackpertime
pusha
pusha
movax,#0xe2e!
loading...message2e=.
movbx,#7
int0x10
popa
movdx,track
movcx,sread
inccx
movch,dl
movdx,head
movdh,dl
anddx,#0x0100
movah,#2
pushdx!
saveforerrordump
pushcx
pushbx
pushax
int0x13
jcbad_rt
addsp,#8!
popax,bx,cx,dx
popa
ret
5、转到Setup程序的第一条指令处,开始执行Setup,完成基本的设置。
jmpi0,SETUPSEG!
SETUPSEG=0x9020
此后,引导程序已经完成了其使命,它的这块内存区域将被Setup程序用来存放来自BIOS的信息。
2.4Setup
Setup做如下工作:
1、检查自己是否被全部读到了SETUPSEG段中。
因为LILO装入程序缺省情况下只读入4个扇区的Setup程序,因此有可能没有读全。
Setup程序的结尾是0xAA555A5A,检查该标志是否存在就可知道它是否完整。
如果不完整,剩余的部分肯定在0x10000处,将其移到Setup的后面,同时调整系统内核的开始位置。
移动程序如下:
movax,cs!
aka#SETUPSEG
subax,#DELTA_INITSEG!
aka#INITSEG
movds,ax
xorbh,bh
movbl,[497]!
getsetupsectsfrom
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第二章 引导和初始化 第二 引导 初始化
![提示](https://static.bingdoc.com/images/bang_tan.gif)