本文首发于我的博客
上一节进行到了kernel_init的printf_kernelinfo,继续往下分析

1.pmm_init

这个函数,顾名思义是用来初始化物理内存的函数,这个函数只会调用gdt_init()

这里我稍微修改了一下代码框架,原本的gdt_pd的定义为注释部分

这段代码看上去没问题,但是实际跑的时候就会发现gdt_pd的两个字段都是0。

我写了一个程序来模拟这种情况,看上去好像没有问题,但这个程序是无法通过编译的。

这个错误告诉我们编译器无法在执行前算出a1的地址。所以不可以这样初始化变量。

至于为什么会这样,改天再研究吧。现在先关注到实验上来。总之,这样改完之后,程序就可以正确运行了。

关于TSS的部分现在先不用管,然后就到了lgdt函数的位置了

我们要执行pmm_init的意义就在于我们需要重新为内核建立段描述符。第一次建立段描述符还是在bootasm中,那时候我们刚从实模式进入保护模式,那时候建立的段描述符有3个

  • 空描述符

  • 代码段描述符

  • 数据段描述符

现在内核需要建立六个描述符

对于我们的操作系统来说,所有的段描述符都是可以访问4G的内存的,唯一的区别在于权限的不同,我们实际上并没有使用分段机制,这叫做 平坦内存模型(Flat memory model)。

在使用lgdt指令加载完gdt之后,刷新各个段寄存器。最后还会执行一条ljmp指令

关于这个ljmp,在实模式进入保护模式时也有涉及,但当时没有注意,现在看到书上的描述,才知道这样做的意义。

  • 使用ljmp指令可以重新加载CS段寄存器,并刷新其对应的缓存器,使其指向正确的地址

  • 在实模式进入保护模式时,这样做的另一个目的是清空流水线,在进入把保护模式时,ljmp之后的指令已经进入了流水线,而且完成了译码阶段,所以要清空流水线,重新按照32位模式加载指令。(理解这部分内容需要了解CPU流水线)

至此,内存的段描述符就建立完成了。gdt_init函数也全部执行完了

2.pic_init

这段程序没有深入的研究,其和操作系统的内核也没有太大的关系,主要是和初始化外设(另一个原因是我也不会…)。暂时先了解:

  • 这个东西是 8259A芯片,pic的全称是(Programmable Interrupt Controller)可编程的中断控制器

  • 所有的外部中断都不直接于CPU进行通讯,而是由8259A统一收集中断请求后再交给CPU

  • 8259A芯片可以编程屏蔽部分外界中断,对于一个中断,其能被处理的先决条件是没有被8259A芯片屏蔽且CPU没有屏蔽外界中断(Eflags 寄存器的 IF 位)

  • 一个8259A只有八个中断引脚,一般使用两个8259A组成级联(Cascade)关系。这样一共支持15个外部中断,我们可以编程来给中断引脚分配中断号

  • outb(IO_PIC1 + 1, IRQ_OFFSET);
    outb(IO_PIC2 + 1, IRQ_OFFSET + 8);
    这两句话比较关键,告诉我们主片的中断号从32开始,从片的从40开始

至于为什么从这里开始,是因为Intel将32以下的终端号保留使用,用户正常情况下可以使用32-255号中断

3.idt_init

初始化完8259之后,就要初始化中断的处理了。

这个部分比较复杂,需要多看几次才能理解,我也尽量讲清楚。

在idt_init中,需要初始化中断描述符表,这个表的位置在数据段中。一共有255个表项,对应着可以接收的255个中断号。由于CPU在接收到中断的时候会根据此项来确定中断处理函数的地址,所以一个idt表项中应当含有中断处理函数的CS:IP值,在这里就是CS段选择子和中断处理程序的偏移量。

我们使用kernel的代码段作为段选择子,偏移由__vectors数组指定。注意,现在所有的段选择子的基址都是0,也即是说,偏移的地址就是实际的物理地址。

__vectors数组存放在数据段中,依据255 * 4 的方式组织,每一项是一个指向对应异常处理函数地址的指针。

异常处理函数有255个,由vector开头加上异常号,这些函数连续的放在代码段中。

每一个异常处理函数的工作都是类似的,先push异常号,然后跳转,至于为什么这样,后文再说。

现在来总结一下一共用到了哪些变量

  • idt[256] 位置:数据段 作用:用来根据异常号选择异常处理程序。
  • __vectors 位置:数据段 作用:用来初始化idt,与中断处理无关。
  • vectorX函数 位置:代码段 作用:实际的异常处理函数。

4.中断

之后就是初始化时钟,使能中断,我们主要来看看中断时如何处理 的。

关于中断的处理,需要分成两个部分,一个是CPU硬件的部分,一个是操作系统的部分。

首先说硬件,ucore的指导书上写的很详细,我这里总结一下,不过先阶段我并没有考虑权限的问题,因为第一个用户进程还没有起来,我们现在一直都在内核态。

cpu的工作为:

  • 再执行每一条指令之后,都检查有没有中断产生

  • 如果有中断,则根据 IDTR 找到 IDT,在根据中断编号找到对应的IDT表项

  • 压入 eflags ,cs ,eip , errorcode

  • 跳转到异常处理函数(根据idt的cs和eip)

cpu执行完之后,栈中就有了eflags,cs,ip和errcode。然后再由操作系统接手。

操作系统的工作:

首先调用的时 vectorX 函数,此函数push 异常号,调用 __alltraps 函数。

__alltraps 压入 ds ,es , fs ,gs再压入所有的通用寄存器,然后将段寄存器换成内核代码段,最后压入 esp,call trap 函数。

trap函数使用C语言写的,需要一个类型为 trapframe 的参数。真个过程的关键,就在于这个参数是怎么传递的。

5. 中断的参数传递

首先我们看一下从中断开始到进入trap函数,栈里究竟多了些什么。

CPU压入了eflags,cs,eip,errorcode。

vectorX压入了中断号

__alltraps压入了ds,es,fs,gs + 通用寄存器 + esp

画出图来就是这个样子。一般来说,函数的调用会由编译器来组织参数的传递,但是我们这次时从汇编语言到C语言只能自己来手动传递参数,更具上一篇文章的经验,我们知道,返回地址上面的一个数据就是函数的最左参数,这里是esp,也就是说,我们将esp当作第一个参数传入了trap函数

当我们压入esp时,esp指向的是我们压入的倒数第二个数据,也就是通用寄存器的最后一个。

再看esp上面的栈结构,比对一下trapframe结构体,会发现竟然是完全一模一样的!

传进去的参数就是一个指向trapeframe的指针,这里需要根据实际的内存来构造trapframe结构体,需要注意的就是,通用寄存器的地址最低(最后入栈),所以在结构体中的第一个,还有就是由于push的都是32位的数据,要对16位的段寄存器进行填充。

这样构造完成之后,在trap函数中就可以快乐的访问trapframe的内容了

当然到现在还只是猜测,我门需要验证一下,打开gdb,扫描一下内存,就会发现:

从高地址往低地址,依次是

0x00000216:eflags

0x00000008:cs (内核数据段,第一个段描述符,偏移8)

0x0010007a: eip(对应lab1里的while(1)代码)

0x00000000:errorcode(这个不太清楚,可能没有)

0x00000020:32,中断号

0x00000010:内核数据段(ds,第二个段描述符,偏移16)

。。。

0x00007b8c:作为参数的esp

0x00102bf1:__alltraps函数的返回地址

事实证明,内存的分布确实如此

6.code

第二个编程任务,要求我们每100个ticks在终端输出100ticks

有了前面的基础,这个就非常简单了,只要在trap_dispath里加几句就行。

效果就是每秒输出一个“100 ticks”,同理,其他的异常也在这个函数里实现。

7.总结

到这里,Lab1就算做完了,整体上入门实验还是比较困难的,要求的前置知识有很多。我也是尽量的弄明白一些部分,还有些不太懂得就先跳过了…

下一篇来回答Lab1提出的的几个问题

如有错误,欢迎指出~

操作系统实验Ucore:Kernel_init(四)相关推荐

  1. 操作系统实验Ucore lab8+反馈队列

    综合实验(lab8+反馈队列) 前言 这是中山大学数据科学与计算机学院2019年操作系统实验中关于Ucore的项目以及实验报告,实验要求与Ucore手则有少量出入. 所有源代码已经上传至github. ...

  2. 操作系统实验—ucore Lab1

    一.内容 通过 Lab1 中的 bootloader 可以从实模式切换的保护模式,然后再读取磁盘并加载 ELF 文件以加载 OS 操作系统,操作系统能够读入字符并显示到屏幕上,具体内容如下: 练习 1 ...

  3. 操作系统实验ucore lab7

    阅读前注意事项: 1.我的博客从lab2之后,如果没有特殊说明,所有标注的代码行数位置,以labcodes_answer(答案包)里的文件为准!!!因为你以后会发现做实验用meld软件比较费时费力,对 ...

  4. 操作系统实验ucore lab4

    阅读前注意事项: 1.我的博客从lab2之后,如果没有特殊说明,所有标注的代码行数位置,以labcodes_answer(答案包)里的文件为准!!!因为你以后会发现做实验用meld软件比较费时费力,对 ...

  5. 操作系统实验ucore lab6

    阅读前注意事项: 1.我的博客从lab2之后,如果没有特殊说明,所有标注的代码行数位置,以labcodes_answer(答案包)里的文件为准!!!因为你以后会发现做实验用meld软件比较费时费力,对 ...

  6. Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)

    前言 刚学完操作系统,模拟实现了其中一些经典的算法,内容比较多,打算写一个系列的总结,将自己的源码都分享出来,既方便自己以后复习,也希望能帮助到一些刚入坑的小伙伴.我的所有代码的运行环境都是基于Ecl ...

  7. (操作系统实验)第四次说明

    数字说明,解决饿死的方法 allnum 和readnum代表的含义是一样的,都是最多可以读取的数量 2 3 3 3 4 A 如果谁都不休息的话,此时 **A B C **会一直连续不断的读,发生饥饿 ...

  8. 操作系统实验报告1:ucore Lab 1

    操作系统实验报告1 实验内容 阅读 uCore 实验项目开始文档 (uCore Lab 0),准备实验平台,熟悉实验工具. uCore Lab 1:系统软件启动过程 (1) 编译运行 uCore La ...

  9. ucore操作系统实验笔记 - Lab1

    最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...

最新文章

  1. php -find(),php – beforeFind()添加条件
  2. 从安全视角对机器学习的部分思考
  3. Yii2与Yii1的模块中Layout使用区别
  4. visudo使用-怎样将mount权限给普通用户
  5. 自学python推荐书籍2019-2019最强Python书单!
  6. Freemarker商品页面静态化
  7. Qt中ui文件的使用
  8. 快速计算文件的MD5/SHA1/SHA256等校验值(Windows/Linux)
  9. 模仿u-boot的makefile结构
  10. 1.Ehcache(01)——简介、基本操作
  11. .NET Framework 4.5的C#中的对话框消息
  12. poj2689Prime Distance
  13. android命名管道创建使用
  14. oracle 体系结构及内存管理 14_锁
  15. Python+OpenCV+PyQt5+多线程实现桌面监控程序
  16. 基于java超市管理系统设计
  17. 9 个 yyds 的 Java 项目,可应对各种私活
  18. mysql的event_mysql中event的用法详解
  19. ARTS 2019 05 05 (29)
  20. java8历史版本下载地址

热门文章

  1. java -cp 的使用
  2. android 涂鸦(清屏,画笔,粗细,保存)以及canvas源码学习
  3. Oracle用OEM和命令行方式创建用户及表空间
  4. 【战网】如果直接使用国服战网客户端登录亚服
  5. 学习编程前需要知道什么?
  6. 将前端网页生成二维码
  7. Hbase之一月速成:Hbase的shell命令
  8. WRMPS经典Cookie欺骗漏洞批量拿下shell-黑客博客
  9. 考研部分概念和流程(若不全和错误可提示我补充,另考研帮app推荐)
  10. html5绘制标尺,html5画布创建标尺