哈工大李治军老师操作系统笔记【2】:操作系统启动(Learning OS Concepts By Coding Them !)

30 篇文章 52 订阅

0 回顾

刚一开始上电,操作系统在磁盘上,计算机是取址执行的,要把代码放到内存上,才能启动,所以要先把OS从磁盘上存入内存,这样才能运行起来,所以第一步就是将磁盘上的OS读入内存,这个工作就由操作系统的引导扇区来完成,即为第一个扇区,bootsect。

1 setup模块

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 CS16+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都在这个空间之上。



1.1 保护模式

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了,就进入了保护模式了,这寻址方式就发生了改变。

1.2 保护模式下的地址翻译和中断处理

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 globaldescriptiontable。但是在这个表中一定要有内容,没有内容怎么选数据呢?所以,这就是setup干的最后一件事,即,初始化表,如下GDT表:


在这里插入图片描述


可以看到setup初始化GDT表时,仅仅保存了两个表项(每个表项64位,8个字节,按照8个字节为单位作为索引),因为setup是在最后进入保护模式的,所以只有最后一条跳转指令使用了GDT查表,所以说setup代码仅仅是在最后简单的使用了GDT,这两个表项都是0x0000,一个只读(代码),一个读写(数据)。


在这里插入图片描述


从这里可以看出,现在的中断也是在这个表中去寻找(保护模式)。

1.3 查找GDT表中的数值,jmpi 0, 8

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代码的作用。

1.4 总结setup

  • 读了一些硬件参数,设置一些数据结构用于管理这些硬件
  • 把system模块移到o地址
  • 初始化GDT表,进入保护模式,跳转到system模块

1.5 system模块

system模块的第一段代a码是什么?head.s,刚才的jmpi,8就是跳到了head.s

  • 从Makefile的角度讲解是如何组织代码为oootsect、setup、system模块这种顺序的
  • Makefile可以控制最终生成的代码的组织结构,然后按照前述的顺序放在硬盘的前面几个扇区中
  • 其中system模块的第一部分代码是head.s,head.s执行完后再执行main.c

编写操作系统,除了源码,还得编写makefile(要控制大型软件的合成结构,必须控制makefile)


在这里插入图片描述


将上图中的image,写入bootsect扇区,初始化,即可启动OS。image包含,bootsect,setup,head.s,main.c把这些模块合起来就是image这个镜像,head.s是syst当中的第一个模块,所以这就是为啥head叫head。

1.6 head.s的作用

  • 再次初始化IDT和GDT表
  • 如下图,head.s的代码和之前看到的oootsect…s和setup的代码不太一样,这是因为head.s是运行在保护模式(32位模式)下的,是32位的汇编代码,而bootsect…s和setupl的代码是16位的汇编代码
  • 另外在c代码中,可以内嵌汇编,达到精细控制的目的,这又是另外一种汇编

在这里插入图片描述


在这里插入图片描述


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,表现出来就是计算机死机了。


在这里插入图片描述


1.7 进入main

  • 传给main函数的三个形参,分别是envp、argv、argc,但是main函数并没有使用
  • main的工作就是x_init:内存、中断、设备、时钟、CPU等内容的初始化

1.8 mem_init()

  • mem_init()就是内存的初始化,如下图
  • 按照4K为单位对内存进行划分区域,mem_map是表示内存区域是否被使用的一个表格
  • end_mem其实就是总内存大小,那这个参数是从哪里来的呢?我们之前讲setup的时候,说了会读取内存大小放在0x90002的位置,就是从这里来的
  • 下面的代码,首先将mem_map全部初始化为USED,然后将start_mem到end_mem之间的内存区域设置为0,即未被使用
  • mem_map前面的部分是USED,这是操作系统代码和一些管理硬件的数据结构所占用的内存

从0开始的都是操作系统内容。
在这里插入图片描述


2 总结

bootsect(将OS从磁盘读进来),setup(获得参数,启动保护模式),head(初始化GDT表,页表),main(一堆mem_init之类的,看有哪些空闲内存之类的,看内存的分配情况),合在一起,就是在启动时候做了两件事,分别是读入内存(为了取址执行)和初始化(OS是管理计算机硬件的软件,所以为了管理硬件,就必须对mem_init之类的初始化,完成对硬件的控制)。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值