1.3.5 head.s开始执行(1)

1.3.5 head.s开始执行(1)

在讲解head程序之前,我们先介绍一下从bootsect到main函数执行的整体技术策略。

在执行main函数之前,先要执行三个由汇编代码生成的程序,即bootsect、setup和head。之后,才执行由main函数开始的用C语言编写的操作系统内核程序。

前面我们讲过,第一步:加载bootsect到0x07C00,然后复制到0x90000;第二步:加载setup到0x90200。值得注意的是,这两段程序是分别加载和分别执行的,head程序与它们的加载方式有所不同。

大致的过程是,先将head.s汇编成目标代码,将用C语言编写的内核程序编译成目标代码,然后链接成system模块。也就是说,system模块里面,既有内核程序,又有head程序,两者是紧挨着的。要点是,head程序在前,内核程序在后,所以head程序名字叫“head”,head程序在内存中占有25KB+184B的空间,请读者注意这个数字。前面讲解过,system模块加载到内存后,setup将system模块复制到0x00000位置,由于head程序在system的前面,所以实际上,head程序就在0x00000这个位置。head程序和以main函数开始的内核程序在system模块中的布局示意图如图1-24所示。

head程序除了做一些调用main的准备工作之外,还做了一件对内核程序在内存中的布局及内核程序的正常运行有重大意义的事,即用程序自身的代码在程序自身所在的内存空间创建了内核分页机制,即在0x000000的位置创建了页目录表、页表、缓冲区、GDT、IDT,并将head程序已经执行过的代码所占的内存空间覆盖,这意味着head程序自己将自己废弃,main函数即将开始执行。

 
图1-24 system在内存中的分布示意图

以上就是head程序执行过程的整体策略,我们参照这个策略,看看head究竟是怎么执行的。

在讲解head程序执行之前,我们先来关注一个标号:_pg_dir。

如下面的代码所示:

 
 
  1. //代码路径:boot/head.s  
  2.  
  3. _pg_dir:  
  4. startup_32:  
  5. movl $0x10,%eax  
  6.     mov %ax,%ds  
  7.     mov %ax,%es  
  8.     mov %ax,%fs  
  9.     mov %ax,%gs 

标号_pg_dir用于标识内核分页机制完成后的内核起始位置,也就是物理内存的起始位置0x000000。head程序马上就要在此处建立页目录表,为分页机制做准备。这一点非常重要,是内核能够掌控用户进程的基础之一,后续章节将逐步讲解。图1-25中描述了页目录表在内存中所占的位置。

 
图1-25 建立内核分页机制

现在head程序正式开始执行,一切都是为适应保护模式做准备。在图1-25中,其本质就是让CS的用法从实模式转变到保护模式。在实模式下时,CS本身就是代码段基址。在保护模式下时,CS本身并不是代码段基址,而是代码段选择符。通过图1-25的分析得知,jmpi 0, 8这句代码使CS和GDT表的第2项关联,并且使代码段的基址指向了0x000000。

从现在开始,要将DS、ES、FS和GS等其他寄存器从实模式转变到保护模式。执行代码如下:

 
 
  1. //代码路径:boot/head.s  
  2.  
  3. startup_32:  
  4. movl $0x10,%eax  
  5.     mov %ax,%ds  
  6.     mov %ax,%es  
  7.     mov %ax,%fs  
  8.     mov %ax,%gs 

执行完毕后,DS、ES、FS和GS中的值都变为0x10。与前面提到的jmpi 0,8中8的分析方法相同,0x10也应看成二进制的00010000,最后三位与前面讲解的一样,其中最后两位的00表示内核特权级、第3位的0表示选择GDT表,第4、5两位的10是GDT表的2项,也就是第3项。也就是说,4个寄存器用的是同一个全局描述符,它们的段基址、段限长和特权级都是相同的,特别要注意的是,影响段限长的关键字段的值是0x7ff,段限长就是8MB。

图1-26的左下部给出了详细示意。

 
图1-26 设置DS、ES、FS、GS

具体的设置方式与图1-23类似,即都要参考GDT表中的内容,movl $0x10,%eax中的0x10是GDT表中的偏移值(用二进制表示就是10000),即要参考GDT表中第2项的信息(GDT表项号排序为第0项、第1项、第2项)来设置这些段寄存器,这一项就是内核数据段描述符。

点评

各段重叠,这样的编码操作方式,需要头脑非常清楚!

SS现在也要转变为栈段选择符,栈顶指针也成为32位的esp,如下所示:


 
 
  1. lss _stack_start,%esp 

在kernel/sched.c中,stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 }这行代码将栈顶指针指向user_stack数据结构的最末位置,这个数据结构是在kernel/sched.c中定义的,如下所示:

 
 
  1. long user_stack [ PAGE_SIZE>>2 ] 

我们测算出其起始位置为0x1E25C。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值