加载"/>
操作系统加载
1.简介
作用:setup.s是操作系统加载程序,它的作用是利用ROM BIOS中断读取系统数据,并将这些数据保存到0x90000开始的位置处(覆盖了原来bootsect程序所在的地方)
读取到数据保存的位置:
将读取到的数据保存后,setup将system模块从 0x10000-0x8ffff整块移动到内存绝对地址0x00000处。
然后加载中断描述符表寄存器(idir)和全局描述符表寄存器(gdtr),开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20-0x2f。再设置cpu的控制寄存器CR0(机器状态字),从而进入32位保护模式,并跳到system模块最前面部分的hean.s程序继续运行。
为了能让head.s在32位保护模式下运行,在程序中临时设置中断描述符(IDT)和全局描述符表(GDT),并在GDT中设置当前内核代码段的描述符和数据段的描述符。
GDT
段描述符存放在描述符表中。描述符表其实就是内存中描述符项的一个阵列。
描述符表有两类:全局描述符表(G60aldescriptor table-GDT)和局部描述符表(Local descri ptantable−LDT)。
处理器是通过使用GDTR和LDTR寄存器来定位GDT表和当前的LDT表。
这两个寄存器以线性地址的方式保存了描述符表的基地址和表的长度。
指令Igd和sgd用于访问GDTR寄存器; 指令Hdt和slut用于访问LDTR寄存器。 lgd使用内存中一个6字节操作数来加载GDTR寄存器。头两个字节代表描述符表的长度,后4个字节是描述符表的基地址。但,访问LDTR寄存器的指令lut所使用的操作数却是一个2字节的操作数,表示全局描述符表GDT中一个描述符项的选择符。该选择符所对应的GDT表中的描述符项应该对应一个局部描述符表。
setup设置的GDT描述符项,代码段描述符的值是0x00C09A0000007FF,
表示代码段的限长是 8MB(=(0x7F+1)∗4KB, 这里加1是因为限长值是从0开始算起的,段在线性地址空间中的基址是0,段类型值009A表示该段存在于内存中、段的特权级别为0、段类型是可读可执行的代码段,段代码是32位的并且段的颗粒度是4KB。
数据段描述符的值是0x00C0920000007FF,表示数据段的限长是8MB…段在线性地址空间中的基址是0。段类型值0x92表示该段存在于内存中、段的特权级别为0、段类型是可读可写的数据段、段代码是32位的并且段的颗粒度是4KB。
逻辑地址的选择符部分用于指定一描述符,它是通过指定一描述符表并且索引其中的一个描述符项完成的。
段选择符格式:
其中索引值用于指定描述符表中8192(2**13)个描述符中的一个。
处理器将该索引值乘上8,并加上描述符表的基地址即可访问表中指定的段描述符。
表指示器(Table Indicator - TD)用于指定选择符所引用的描述符表。值为0表示指定GDT表,值为1表示指定当前的LDT表。请求者特权级(R capaestor’sPrivalege Level-RPL)用于保护机制。
由于GDT表的第一项(索引值为0)没有被使用,因此一个具有索引值0和表指示器值也为0的选择符(也即指向GDT的第一项的选择符)可以用作为一个空(null)选择符。当一个段寄存器(不能是 CS或SS)加载了一个空选择符时,处理器并不会产生一个异常。但是若使用这个段寄存器访问内存时就会产生一个异常。对于初始化还未使用的段寄存器以陷入意外的引用来说,这个特性是很有用的。
在进入保护模式之前,我们必须首先设置好将要用到的段描述符表,例如全局描述符表GDT。然后使用指令lgdt把描述符表的基地址告知CPU(GDT表的基地址存入g知寄存器)。再将机器状态字的保护模式标志置位即可进入32位保护运行模式。
Linux 0.11硬盘设备号
在Linux中,硬盘的主区号是3,其他设备的主设备号分别为:
1–内存
2–磁盘
3–硬盘
4–ttyx
5–tty
6–并行口
7–非命名管道
一个硬盘可以有1-4个分区,可以依据分区的不同用次设备号进行指定分区,所以:设备号=主设备号*256+次设备号
磁盘
一个磁盘由多个盘片(如下图中的 0 号盘片)叠加而成。盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面
每个盘片被划分为一个个磁道,每个磁道又划分为一个个扇区
柱面
每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。所有盘面中相对位置相同的磁道组成柱面
磁盘的物理地址
可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”
可根据该地址读取一个“块”,操作如下:
① 根据“柱面号”移动磁臂,让磁头指向指定柱面;
② 激活指定盘面对应的磁头;
③ 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写
2.源码分析
1.setup完成OS前的初始化和设置
1)保存光标的位置
2)得到扩展内存的大小
3)得到显示卡当前的显示模式
4)检测显示方式
5)读取硬盘参数表信息
硬盘基本参数表(INT 0x41)
在中断向量表中,int 0x41的中断向量位置(4*0x41=0x0000:0x0140)存放的不是中断程序的地址入口,而是第一个硬盘参数表的信息,0x46存放第二个硬盘参数表
硬盘参数表信息:
2.将整个system模块移动到0x00000处
; bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面
; 下面这段程序将整个system模块移动到0x00000处,
; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置mov ax,#0x0000cld ! 'direction'=0, movs moves forward
do_move:mov es,ax ! destination segmentadd ax,#0x1000cmp ax,#0x9000 ! 判断代码是否移动完成jz end_move ! 移动完成则跳转mov ds,ax ! source segmentsub di,disub si,simov cx,#0x8000 ! 循环移动,循环次数,每次循环完次数减 移动0x8000字rep ! 用于把内容从ds:si 复制es:di 以字节单位movsw ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中jmp do_move
移动后内存存放数据:
3.跳转到绝对地址0x00000处
在跳转之前还要进行相应的设置
1)加载段描述符,设置全局描述符表和中断描述表
2)开启A20地址线,为了能够访问和使用1MB以上的物理内存
3)重新对中断进行编程
进入保护模式:jmpi 0,8
;进入保护模式,只是跳转到绝对地址0x00000处; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式mov ax,#0x0001 ! protected mode (PE) bit 保护模式比特位(PE)lmsw ax ! This is it! 加载状态寄存器;段选择符8表示请求特权0级,使用GDT第二个段描述符jmpi 0,8 ! jmp offset 0 of segment 8 (cs) 跳转至cs段偏移地址位0处(system已经移动到0x00000处)
setup程序完整代码
!
! setup.s (C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!
; setup从BIOS中获取数据,并将这些数据保存到0x90000开始的位置处(0x90000-0x901FF覆盖了原来bootsect程序所在的地方)
; 此时setup和system已经由bootsect引导块加载到内存中
;
! NOTE! These had better be the same as in bootsect.s!INITSEG = 0x9000 ! we move boot here - out of the way 原来bootsect所在段
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). system所在0x10000处
SETUPSEG = 0x9020 ! this is the current segment 本程序所在段地址.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.textentry start
start:! ok, the read went well so we get current cursor position and save it for
! posterity.
; 保存光标的位置
; 使用BIOS中断取屏幕当前光标的位置(列,行),保存到内存(0x90000)处,2个byte
; 控制台初始化程序会到此处读取该值
; BISO 中断0x10 功能号 ah = 0x30 ,读光标的位置
; 输入:bh=页号
; 返回:返回:ch = 扫描开始线,cl = 结束开始线,dh = 行号(0x00顶端),dl=列号(0x00最左边)mov ax,#INITSEG ! this is done in bootsect already, but...mov ds,axmov ah,#0x03 ! read cursor pos 功能号 ah = 0x30 ,读光标的位置xor bh,bhint 0x10 ! save it in known place, con_init fetchesmov [0],dx ! it from 0x90000. 将ds设置成0x90000(INITSEG)! Get memory size (extended mem, kB)
; 得到扩展内存的大小
; 利用BIOS中断0x15 功能号 ah= 0x88取系统所含扩展内存大小并保存到0x90002处
; 返回: ax= 0x10000(1M)处开始的扩展内存大小,若出错CF置位,ax=出错码mov ah,#0x88int 0x15mov [2],ax !扩展内存的大小保存到0x90002处! Get video-card data:
; 得到显示卡当前的显示模式
; 调用BIOS中断0x10,功能号 ah = 0x0f
; 返回:ah=字符列数,al=显示模式,bh=显示当前页数mov ah,#0x0fint 0x10mov [4],bx ! bh = display pagemov [6],ax ! al = video mode, ah = window width! check for EGA/VGA and some config parameters
; 检测显示方式
; 调用BIOS中断0x10, 功能号 ah=0x12,bl=0x10mov ah,#0x12mov bl,#0x10int 0x10mov [8],ax ! 0x90008 =axmov [10],bx ! 0x9000A = 安装的显示内存,0x9000B = 显示状态mov [12],cx !0X9000C = 显卡特性参数! Get hd0 data
; 取第一个硬盘信息
; 第一个硬盘参数表的首地址是中断向量0x41的向量值
; 第二个紧跟着对应着中断向量0x46
; 下面两个程序分别复制BIOS有关硬盘参数表,
; 第一个硬盘存放在0x90080,第二个硬盘存放在0x90090mov ax,#0x0000mov ds,axlds si,[4*0x41] !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:simov ax,#INITSEG mov es,axmov di,#0x0080 !传输的目的地址(0x9000:0x0080) -->es:dimov cx,#0x10 ! 循环次数,每次循环完次数减一,共传输16个字节rep ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中movsb ! 用于把内容从ds:si 复制es:di 以字节单位! Get hd1 datamov ax,#0x0000mov ds,axlds si,[4*0x46] !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:simov ax,#INITSEG mov es,axmov di,#0x0090mov cx,#0x10repmovsb! Check that there IS a hd1 :-)
; 检测是否有第二个硬盘,如果没有则把第2个清零
; 利用BIOS中断调用0x13的取盘的类型,功能号 ah =0x15mov ax,#0x01500mov dl,#0x81 ! dl = 驱动器号(0x8X是硬盘,0x81是第一个硬盘,0x82是第二个硬盘)int 0x13jc no_disk1 ! 第二个不存在cmp ah,#3 ! ah =类型码 指硬盘je is_disk1 ! 存在; 第二个硬盘不存在,对第二个硬盘表清零
no_disk1:mov ax,#INITSEGmov es,axmov di,#0x0090mov cx,#0x10mov ax,#0x00repstosb
; 第二个硬盘存在,进入保护模式,从此开始不允许中段
is_disk1:! now we want to move to protected mode ...cli ! no interrupts allowed !! first we move the system to it's rightful place
; bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面
; 下面这段程序将整个system模块移动到0x00000处,
; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置mov ax,#0x0000cld ! 'direction'=0, movs moves forward
do_move:mov es,ax ! destination segmentadd ax,#0x1000cmp ax,#0x9000 ! 判断代码是否移动完成jz end_move ! 移动完成则跳转mov ds,ax ! source segmentsub di,disub si,simov cx,#0x8000 ! 循环移动,循环次数,每次循环完次数减 移动0x8000字rep ! 用于把内容从ds:si 复制es:di 以字节单位movsw ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中jmp do_move! then we load the segment descriptors
; 加载段描述符,设置全局描述符表和中断描述表end_move:mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)mov ds,ax
; lidt指令用于加载中断描述符表(IDT)寄存器
; 中断描述符表中每一个8个字节对应每个中断发生时所需要的中断程序地址入口lidt idt_48 ! load idt with 0,0
; lgdt指令用于加载全局描述符表(GDT)寄存器
; 全局描述符表中每个描述符项(8字节)描述了保护模式下数据段和代码段的信息lgdt gdt_48 ! load gdt with whatever appropriate! that was painless, now we enable A20
; 开启A20地址线,为了能够访问和使用1MB以上的物理内存call empty_8042 ! 测试8042状态寄存器,等待输入缓冲器空,mov al,#0xD1 ! command write 0xD1命令码表示写数据到8042的P2端口out #0x64,alcall empty_8042 !等待输入缓冲器空,看命令是否被接受mov al,#0xDF ! A20 onout #0x60,alcall empty_8042 !若此时输入缓冲器为空,则表示A20线也选通 ! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.; 重新对中断进行编程mov al,#0x11 ! initialization sequenceout #0x20,al ! send it to 8259A-1 发送到8259A主芯片
; 0x00eb直接使用机器码表示两条相对跳转指令,起延时作用.word 0x00eb,0x00eb ! jmp $+2, jmp $+2out #0xA0,al ! and to 8259A-2 再发送到8259A从芯片.word 0x00eb,0x00eb
; 系统硬件中断号被设置成0x20开始mov al,#0x20 ! start of hardware int's (0x20)out #0x21,al !送主芯片ICW2命令字,设置起始中断,要送奇端口.word 0x00eb,0x00ebmov al,#0x28 ! start of hardware int's 2 (0x28)out #0xA1,al !送主芯片ICW2命令字,从芯片的起始中断号.word 0x00eb,0x00ebmov al,#0x04 ! 8259-1 is masterout #0x21,al !ICW3.word 0x00eb,0x00ebmov al,#0x02 ! 8259-2 is slaveout #0xA1,al.word 0x00eb,0x00ebmov al,#0x01 ! 8086 mode for bothout #0x21,al.word 0x00eb,0x00ebout #0xA1,al.word 0x00eb,0x00ebmov al,#0xFF ! mask off all interrupts for nowout #0x21,al.word 0x00eb,0x00ebout #0xA1,al! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.; 进入保护模式,只是跳转到绝对地址0x00000处; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式mov ax,#0x0001 ! protected mode (PE) bit 保护模式比特位(PE)lmsw ax ! This is it! 加载状态寄存器;段选择符8表示请求特权0级,使用GDT第二个段描述符jmpi 0,8 ! jmp offset 0 of segment 8 (cs) 跳转至cs段偏移地址位0处(system已经移动到0x00000处)! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.; 检差键盘命令队列是否为空
; 只有当输入缓冲器为空(键盘控制器状态寄存器位1 = 0)才可以进行写命令
empty_8042:.word 0x00eb,0x00eb !延时作用in al,#0x64 ! 8042 status porttest al,#2 ! is input buffer full?jnz empty_8042 ! yes - loopret; GDT全局描述符表开始处,描述符表由多个8字节长的描述符项组成,
; 3个描述符项
; 第一项没有作用,但是必须存在
; 第二项是系统代码段描述符
; 第三项是系统数据段描述符
gdt:.word 0,0,0,0 ! dummy 第一个描述符 不用; 在GDT表这里的偏移量是0x80,它是内核代码段选择符的值.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ! base address=0.word 0x9A00 ! code read/exec.word 0x00C0 ! granularity=4096, 386; 在GDT表这里的偏移量是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)
; 这里设置一个长度为0的空表
idt_48:.word 0 ! idt limit=0.word 0,0 ! idt base=0L; 加载全局描述符表寄存器(gdtr)
; GDT表长度为2kb
gdt_48:.word 0x800 ! gdt limit=2048, 256 GDT entries.word 512+gdt,0x9 ! gdt base = 0X9xxxx.text
endtext:
.data
enddata:
.bss
endbss:
更多推荐
操作系统加载
发布评论