操作系统实验Ucore:Kernel_init(四)
本文首发于我的博客
上一节进行到了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(四)相关推荐
- 操作系统实验Ucore lab8+反馈队列
综合实验(lab8+反馈队列) 前言 这是中山大学数据科学与计算机学院2019年操作系统实验中关于Ucore的项目以及实验报告,实验要求与Ucore手则有少量出入. 所有源代码已经上传至github. ...
- 操作系统实验—ucore Lab1
一.内容 通过 Lab1 中的 bootloader 可以从实模式切换的保护模式,然后再读取磁盘并加载 ELF 文件以加载 OS 操作系统,操作系统能够读入字符并显示到屏幕上,具体内容如下: 练习 1 ...
- 操作系统实验ucore lab7
阅读前注意事项: 1.我的博客从lab2之后,如果没有特殊说明,所有标注的代码行数位置,以labcodes_answer(答案包)里的文件为准!!!因为你以后会发现做实验用meld软件比较费时费力,对 ...
- 操作系统实验ucore lab4
阅读前注意事项: 1.我的博客从lab2之后,如果没有特殊说明,所有标注的代码行数位置,以labcodes_answer(答案包)里的文件为准!!!因为你以后会发现做实验用meld软件比较费时费力,对 ...
- 操作系统实验ucore lab6
阅读前注意事项: 1.我的博客从lab2之后,如果没有特殊说明,所有标注的代码行数位置,以labcodes_answer(答案包)里的文件为准!!!因为你以后会发现做实验用meld软件比较费时费力,对 ...
- Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)
前言 刚学完操作系统,模拟实现了其中一些经典的算法,内容比较多,打算写一个系列的总结,将自己的源码都分享出来,既方便自己以后复习,也希望能帮助到一些刚入坑的小伙伴.我的所有代码的运行环境都是基于Ecl ...
- (操作系统实验)第四次说明
数字说明,解决饿死的方法 allnum 和readnum代表的含义是一样的,都是最多可以读取的数量 2 3 3 3 4 A 如果谁都不休息的话,此时 **A B C **会一直连续不断的读,发生饥饿 ...
- 操作系统实验报告1:ucore Lab 1
操作系统实验报告1 实验内容 阅读 uCore 实验项目开始文档 (uCore Lab 0),准备实验平台,熟悉实验工具. uCore Lab 1:系统软件启动过程 (1) 编译运行 uCore La ...
- ucore操作系统实验笔记 - Lab1
最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...
最新文章
- php -find(),php – beforeFind()添加条件
- 从安全视角对机器学习的部分思考
- Yii2与Yii1的模块中Layout使用区别
- visudo使用-怎样将mount权限给普通用户
- 自学python推荐书籍2019-2019最强Python书单!
- Freemarker商品页面静态化
- Qt中ui文件的使用
- 快速计算文件的MD5/SHA1/SHA256等校验值(Windows/Linux)
- 模仿u-boot的makefile结构
- 1.Ehcache(01)——简介、基本操作
- .NET Framework 4.5的C#中的对话框消息
- poj2689Prime Distance
- android命名管道创建使用
- oracle 体系结构及内存管理 14_锁
- Python+OpenCV+PyQt5+多线程实现桌面监控程序
- 基于java超市管理系统设计
- 9 个 yyds 的 Java 项目,可应对各种私活
- mysql的event_mysql中event的用法详解
- ARTS 2019 05 05 (29)
- java8历史版本下载地址