赞
踩
刚一开始上电,操作系统在磁盘上,计算机是取址执行的,要把代码放到内存上,才能启动,所以要先把OS从磁盘上存入内存,这样才能运行起来,所以第一步就是将磁盘上的OS读入内存,这个工作就由操作系统的引导扇区来完成,即为第一个扇区,bootsect。
setup做了一件什么事?
int 0x15
这个代码有什么作用?其要获得物理内存的大小,这也是一个Bios中断,将
#
0
x
88
\#0x88
#0x88作为参数,然后用int 0x15
这个Bios中断,来进行获取内存的大小,获取的值放到
a
x
ax
ax当中,紧接着
a
x
ax
ax又要赋值给
[
2
]
[2]
[2],这个
[
2
]
[2]
[2]是什么?这是间接寻址,
[
2
]
[2]
[2]前面默认有一个段寄存器,这里的段寄存器还一直保留之前的,现在所有的段都指向了9000这个地方,所以地址就是段基址
C
S
∗
16
+
I
P
CS*16+IP
CS∗16+IP,也就是90002这个地址。(mov [2], ax
将
a
x
ax
ax 中内容传递至内存地址
d
s
:
[
2
]
ds:[2]
ds:[2] 处 即
0
x
90002
0x90002
0x90002 处,
a
x
ax
ax 中保存的值为调用int 0x15
中断后获取的扩展内存大小)
那么,为什么要扩展内存?OS是管理计算机硬件的,当然,内存是非常重要的一部分,所以OS要管理内存,所以第一件事就是要知道内存多大,其次,还要知道这个内存,多少倍占用了,被什么占用了。管理内存,就得对内存有个认知,要形成一个数据结构DSA来保存信息。
所以开机后,OS就做了两件事,第一件事,读入系统bootsect,第二件事就是setup,初始化。
指令do_move
的作用,就是把全部代码,弄到0地址处,所以现在开始,从0地址处,就是操作系统的内容,总共0x8000的地址空间,将来操作系统的代码将会一直放在这个位置上。在前一小节中,bootsect代码首先会将自身从0x07c0:0x0000处移动到0x9000:0x0000处,接下来读入的setup模块也紧跟在移动后的bootsect代码后,这么做就是为了给比时将system)放在0x0000~0x8000腾出空间。
如下,OS占用了从0地址开始的地址空间,APP都在这个空间之上。
setup在最后的时候执行了jmpi 0, 8
这条指令,因为setup要退出了,但是OS不能停止,一般来说,0赋值给IP,8赋值给CS,这下算出地址为80,但是跳到80是个非法指令,跳到80的地方,后果就是死机,所以不该跳到80,应该跳到0地址,所以从这时候,寻址发现了改变。(0,8应该表示的是逻辑地址,8:段选择符,0:偏移量,通过段选择符8定位到GDT表中第2个描述符(第一个默认不使用),通过描述符中的段基地址+偏移量得到线性地址。具体请参考完全注释第四章和第六章)
CS和IP都是16位的寄存器,CS<<4+IP最多能够达到20位地址,也就是1M的地址空间,1M肯定是不够的,无法满足现代计算机的要求,现在要从1M变成4G,即32位模式。从16位模式切换到32位模式,切换保护模式,怎么来做呢。
通过指令mov cr0, ax
来做,可以将实模式理解成16位模式,保护模式理解成32位模式。cr0寄存器32位,最高位是PG,最低位是PE,PE=1表示启动保护模式,PE=0则还是原来的16位实模式,PG=1表示启动分页。
所以mov cr0, ax
可以看出,把1传递给ax,ax最后一位是1,把这个1再传递给cr0,那么cr0的最后一位就是1了,所以啊,这两条指令一旦执行完了之后,这个PE=1了,就进入了保护模式了,这寻址方式就发生了改变。
GDT产生32位地址,主要目的就是“快”。以前时候,CS当中就是放的段基址,左移四位加上IP就是地址,但是现在CS当中放的是查表的索引,成为了一个选择子,也就是看CS中的内容是多少,然后找到表中对应索引项中的数值,jmpi 0, 8
这条指令,CS=8,所以从表中,找到索引为8的内容,从当中取出基址,再加上偏移地址,组成地址。然后这个表,也就是GDT表,
g
l
o
b
a
l
−
d
e
s
c
r
i
p
t
i
o
n
−
t
a
b
l
e
global-description-table
global−description−table。但是在这个表中一定要有内容,没有内容怎么选数据呢?所以,这就是setup干的最后一件事,即,初始化表,如下GDT表:
可以看到setup初始化GDT表时,仅仅保存了两个表项(每个表项64位,8个字节,按照8个字节为单位作为索引),因为setup是在最后进入保护模式的,所以只有最后一条跳转指令使用了GDT查表,所以说setup代码仅仅是在最后简单的使用了GDT,这两个表项都是0x0000,一个只读(代码),一个读写(数据)。
从这里可以看出,现在的中断也是在这个表中去寻找(保护模式)。
8,8个字节开始,也就是第二行开始。
看要跳到哪里去执行,可以看出,32位,也就是4个字节,两个字,从低到高,分别放入GDT表项,即可知道各个地址。
将GDT进行拆分,总共4个字,也就是64位存放4个字,分别将4个字存放入GDT表中,低16位放入了第一个,也就是0x07FF,之后段基址也就是0x0000,这就是段基址当中的16位,一半,之后,第16位到23位,是第三个的低8位,也就是00,之后24位到31位,剩下的位就放剩下的东西。就是第四个的高8位,00,综上,段基址就是32位的0。所以得出来的就是0地址,接下来跳到0地址处即可,也就是那个jmpi
代码的作用。
system模块的第一段代a码是什么?head.s,刚才的jmpi,8就是跳到了head.s
编写操作系统,除了源码,还得编写makefile(要控制大型软件的合成结构,必须控制makefile)
将上图中的image,写入bootsect扇区,初始化,即可启动OS。image包含,bootsect,setup,head.s,main.c把这些模块合起来就是image这个镜像,head.s是syst当中的第一个模块,所以这就是为啥head叫head。
head.s要跳到main.c,从汇编跳到c函数。
但是最后c函数还是会变成汇编,所以准确来说是从c函数跳到c函数。
所以,C代码其实也会编译成汇编代码,所以从head.s跳转到main.c实际上很简单,就是把参数和main.c的地址压入栈中,在设置页表的代码执行完后,会执行ret返回指令,那么就把栈中main.c的地址作为返回地址,达到了跳转到main.c的效果,设置页面setup_paging的具体代码这里省略,后面再讲。
如果main.c执行结束,会跳转到L6,L6是一个死循环;实际上,正常情况下,main.c就会一直运行下去,不会执行结束,如果main.c结束了,就会跳到6,表现出来就是计算机死机了。
从0开始的都是操作系统内容。
bootsect(将OS从磁盘读进来),setup(获得参数,启动保护模式),head(初始化GDT表,页表),main(一堆mem_init之类的,看有哪些空闲内存之类的,看内存的分配情况),合在一起,就是在启动时候做了两件事,分别是读入内存(为了取址执行)和初始化(OS是管理计算机硬件的软件,所以为了管理硬件,就必须对mem_init之类的初始化,完成对硬件的控制)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。