支持实模式保护模式跳转以及系统调用的操作系统的实现

Nuisance

发布日期: 2019-03-27 09:14:48 浏览量: 502
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

一、实验概述

1.1 实验目标

  • 基于虚拟机平台调试、测试和运行

  • 实现引导程序及自启动装入

  • 实现从实模式到保护模式的切换

  • 系统态实现键盘输入和显示器基础功能

  • 建立控制台及命令接口,在用户态下调用系统态下的终端基础功能支持用户的键盘输入操作和屏幕回显

1.2 实验环境及工具

  • OS:Ubuntu 16.04、Mac OS

  • 虚拟机:VMware Fusion

  • 模拟器:bochs 2.4(Mac)、bochs 2.6.9(Linux)

  • 二进制文件查看器UltraEdit

1.3 实验工具介绍

1.3.1 bochs

Bochs是一个x86硬件平台的开源模拟器。它可以模拟各种硬件的配置。Bochs模拟的是整个PC平台,包括I/O设备、内存和BIOS。更为有趣的是,甚至可以不使用PC硬件来运行Bochs。事实上,它可以在任何编译运行Bochs的平台上模拟x86硬件。通过改变配置,可以指定使用的CPU(386、486或者586),以及内存大小等。一句话,Bochs是电脑里的“PC”。根据需要,Bochs还可以模拟多台PC,此外,它甚至还有自己的电源按钮。

1.3.2 UltraEdit

UltraEdit 是一套功能强大的文本编辑器,可以编辑文本、十六进制、ASCII 码,完全可以取代记事本(如果电脑配置足够强大),内建英文单字检查、C++ 及VB 指令突显,可同时编辑多个文件,而且即使开启很大的文件速度也不会慢。

UltraEdit 是 Windows 旗下一款流行的老牌文本/HEX 编辑器(非开源)。UltraEdit 正被移植到Linux 平台。该移植名为 UEX,意即 UltraEditfor Linux。UEX具有原生的Linux 外观,其界面、配置、热键等与 Windows 版并无二致。

UltraEdit是一个49.95美元的共享软件,提供了友好界面的编程编辑器,支持语法高亮,代码折叠和宏,以及一大堆其他的功能,内置了对于HTML、PHP和JavaScript等语法的支持。

UltraEdit代码折叠支持在所有 32 位Windows平台上进行64 位文件处理(标准),Unicode 支持基于磁盘的文本编辑和大文件处理 - 支持超过4GB 的文件,即使是数兆字节的文件也只占用极少的内存。

二、模块划分

2.1 引导区boot.asm

2.1.1 概述

其实计算机从加电开始就开始运行了,操作系统先进行一系列的自检,这个自检是通过计算机加电然后发送一个信号给BIOS,自动运行主板BIOS芯片固化的程序,通常称为POST。自检完成之后,计算机就开始自动读取磁盘,磁盘读取一个扇区,512个字节,当读到某个磁盘的第0磁道第一个扇区的最后两个字节为55和aa的时候计算机就自动认为其为引导程序,就会把此512字节读入内存的绝对地址0x07c00处,此时计算机的操作就完全交给操作系统设计者了。

那么,也就是说引导系统必须具备如下因素:

  • 加载到0x7c00处

  • 大小为512字节

  • 最后两个字节为55和aa

满足这三个条件的程序都可以作为引导,只要将其写入磁盘的第0磁道第一扇区处即可成为引导。

2.1.2 程序流程图

2.1.3 流程叙述

上图是软盘中的结构,包括引导扇区、FAT1、FAT2、根目录区、数据区,每一个扇区占用512个字节,FAT1、FAT2是两个完全相同的FAT表,表中的数据表示的是文件对应的下一个簇号,根目录区里存放的是若干个目录条目,每个条目占32位,其中有用的就是文件名和开始簇号,计算出loader.bin的数据位置,将其数据放到根目录区(因为之后根目录区不再使用),跳到根目录区对应位置开始执行loader。

2.1.4 主要函数介绍

ReadSector

  • 作用:从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中

  • 计算柱面号ch、起始扇区号cl、磁头号dh

  • 通过调用int 13h中断读al个扇区

由扇区号求扇区在磁盘中的位置:

设扇区号为x:

GetFATEntry

作用:找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中。

2.1.5 运行截图

实模式下打印了“JASON CAI”,以及“Real Mode”。“L”是加载完Loader之后的标志。

2.2 突破512字节的限制loader.asm

2.2.1 概述

一个操作系统从开机到开始运行,大致经历”引导—>加载内核入内存—>跳入保护模式—>开始执行内核”这样一个过程。也就是说,在内核开始执行之前不但要加载内核,还要准备保护模式等一系列工作,如果全部交给引导扇区来做,512字节很可能不够用,所以,不放把这个过程交给另外的模块来完成,我们把这个模块叫做Loader。引导扇区负责把Loader加载如内存并且把控制权交它,其他的工作放心地交给 Loader来做,因为它没有512字节的限制,将会灵活很多。

结合上一节所介绍的FAT12数据结构,从中我们可以知道,要寻找一个文件,首先需要在根目录区中寻找该文件的根目录条目;然后根据根目录条目获取文件开始簇数(也就是在数据区中存储的扇区);最后读取文件内容到内存。

在本节中主要关注实模式到保护模式的跳转,其余的粗略带过。

2.2.2 实模式

实模式:(即实地址访问模式)它是Intel公司80286及以后的x86(80386,80486和80586等)兼容处理器(CPU)的一种操作模式。实模式被特殊定义为20位地址内存可访问空间上,这就意味着它的容量是2的20次幂(1M)的可访问内存空间(物理内存和BIOS-ROM),软件可通过这些地址直接访问BIOS程序和外围硬件。实模式下处理器没有硬件级的内存保护概念和多道任务的工作模式。但是为了向下兼容,所以80286及以后的x86系列兼容处理器仍然是开机启动时工作在实模式下。80186和早期的处理器仅有一种操作模式,就是后来我们所定义的实模式。实模式虽然能访问到1M的地址空间,但是由于BIOS的映射作用(即BIOS占用了部分空间地址资源),所以真正能使用的物理内存空间(内存条),也就是在640k到924k之间。1M地址空间组成是由16位的段地址和16位的段内偏移地址组成的。用公式表示为:物理地址=左移4位的段地址+偏移地址。

8086CPU数据总线为16位,也就是一次最多能取2^16=64KB数据,这个数据也解释了实模式下为什么每个段最大只有64KB。但刚才还说了其地址总线为20位,这样它能寻址的能力其实是2^20=1MB,这也就是实模式下CPU的最大寻址能力。既然它有1MB寻址能力,那怎么用16位的段寄存器表示呢?

这就引出了分段的概念,8086CPU将1MB存储空间分成许多逻辑段,每个段最大限长为64KB(但不一定就是64KB)。这样每个存储单元就可以用“段基地址+段内偏移地址”表示。段基地址由16位段寄存器值左移4位表达,段内偏移表示相对于某个段起始位置的偏移量。

2.2.3 保护模式

保护模式:286处理器体系结构引入了地址保护模式的概念,处理器能够对内存及一些其他外围设备做硬件级的保护设置(保护设置实质上就是屏蔽一些地址的访问)。使用这些新的特性,然而必不可少一些额外的在80186及以前处理器没有的操作规程。自从最初的x86微处理器规格以后,它对程序开发完全向下兼容,80286芯片被制作成启动时继承了以前版本芯片的特性,工作在实模式下,在这种模式下实际上是关闭了新的保护功能特性,因此能使以往的软件继续工作在新的芯片下。直到今天,甚至最新的x86处理器都是在计算机加电启动时都是工作在实模式下,它能运行为以前处理器芯片写的程序。

寻址方式:下图是逻辑地址到线性地址转换,这里的逻辑地址即指保护模式下的“段选择符+段内偏移地址”,如果不启用分页管理的情况下,那么此线性地址即最终的物理地址。

如下图段选择符结构,段选择符为16位,它不直接指向段,而是通过指向的段描述符,段描述符(一会介绍)再定义段的信息。

其中TI用来指明全局描述符表GDT还是局部描述符表LDT,RPL表示请求特权级,索引值为13位,所以从这里看出,在保护模式下最多可以表示2^13=8192个段描述符,而TI又分GDT和LDT,所以一共可以表示81922=16384个段描述符,每个段描述符可以指定一个具体的段信息,所以一共可以表示16384个段。而图2看出,段内偏移地址为32位值,所以一个段最大可达4GB,这样163844GB=64TB,这就是所谓的64TB最大寻址能力,也即逻辑地址/虚拟地址。

在保护模式实际编程中,如下一条语句:jmpi 0, 8。其中的8即段选择符,8的二进制表示为:0000 0000 0000 1000b,所以这条语句的意思是跳转到GDT表(TI=0)中的第2个(段描述符表从0开始编号,所以这里的1指表中的第2个)段描述符定义的段中,其段内偏移为0。

下面再来看段描述符结构,段描述符表中的每一项为一个段描述符,每一项为8字节,其结构如下图所示。

段选择符指向的段描述符里有三个部分基地址信息,这三部分组成一个32位地址就决定了段基地址位置,此地址再加上段内偏移最终确定线性地址位置。

2.2.4 运行截图

跳入保护模式之后,打印一句话:“This is Protect Mode Jason Cai”,之后开始内核的加载,以及内核的运行:显示“K”。

2.3 内核kernel.asm

2.3.1 内核执行过程概述

kernel的工作流程:

  • kernel.asm

    • _start:内核入口,顺序往下执行
    • cstart():将loader中的GDT复制到内核、设置gdt和ldt,初始化中断向量表
    • init_prot():初始化8259A,初始化各个中断门
  • main.c/tinix_main():

    • 设置PCB信息
  • restart()

    • lldt、ss0、恢复段寄存器和通用寄存器、进入ring1(iretd),执行TestA——无限循环while(1)

2.3.2 进程的创建

进程表与GDT的关系

进程表里的LDT Selector对应GDT中的一个描述符,而这个描述符所指向的内存空间就存在与进程表内。

进程表与进程体的关系

进程表是进程的描述,进程运行过程中如果被中断,各个寄存器的值都会被保存进进程表中。使用到进程表堆栈。但是,在我们的第一个进程开始前并不需要初始化太多内容,只需知道进程的入口地址就足够了,同时需要设置esp,指向进程表。

GDT与TSS的关系

GDT中需要有一个描述符来对应TSS,需要事先初始化这个描述符。

初始化过程概述

进程结构体,结构体PROCESS

进程结构体有了,下面我们在global.c中声明一个进程表

  1. PUBLICPROCESS proc_table[NR_TASKS]

好了,进程表有了下面我们来初始化它。当NR_TASKS=1时,就相当于定义了一个proc_table。这里要记得把LDT跟GDT是联系在一起的,别忘了填充GDT中进程的LDT的描述符。最后在protect.c中的init_Prot()中初始化TSS以及对应的描述符。

各表介绍

任务状态描述符表TSS用来记录当前进程执行时所对应的寄存器的数据,这些数据主要在进程切换时发挥作用,比如,现在要由当前进程”进程A”切换到进程B,那么系统就要将此时各个寄存器的数值,保存在进程A的任务状态描述符表中,以便将来进程A再次执行时接着使用而不至于出现混乱;之后,再用进程B中TSS里面的寄存器值,来设置相应的寄存器,以此支持进程B接下来的执行。

局部数据描述符表LDT中,记录着当前进程对应程序的代码段和数据段信息,比如代码的基地址等,这些信息将在进程程序执行时提供支持。

系统将来就是通过GDT表中挂接的TSS描述符合LDT描述符,来与当前进程建立关系的,这里将TSS和LDT挂接在全局描述符表GDT中,标志着系统从此具备操作进程1的能力。

2.3.3 主要数据结构

  1. typedef struct s_stackframe {
  2. t_32 gs;
  3. t_32 fs;
  4. t_32 es;
  5. t_32 ds;
  6. t_32 edi;
  7. t_32 esi;
  8. t_32 ebp;
  9. t_32 kernel_esp;
  10. t_32 ebx;
  11. t_32 edx;
  12. t_32 ecx;
  13. t_32 eax;
  14. t_32 retaddr;
  15. t_32 eip;
  16. t_32 cs;
  17. t_32 eflags;
  18. t_32 esp;
  19. t_32 ss;
  20. }STACK_FRAME; //进程表,进程表结构的定义,包括进程运行过程中各种寄存器的值。
  21. typedef struct s_proc {
  22. STACK_FRAME regs;/* 进程运行过程中各种寄存器的值*/
  23. t_16 ldt_sel; /*ldt选择子*/
  24. DESCRIPTOR ldts[LDT_SIZE]; /*代码和数据选择子*/
  25. int ticks;
  26. int priority;
  27. t_32 pid; /*进程号*/
  28. char name[16]; /*进程名*/
  29. int nr_tty; /*进程对应的tty*/
  30. }PROCESS; //进程结构体,包含了以上的数据。

2.3.4 运行截图

进程运行的过程中重复打印“caijie”字符串。

2.3.5 键盘中断

8259A工作原理

当一个中断请求从IR0到IR7中的某根线到达IMR时,IMR首先判断此IR是否被屏蔽,如果被屏蔽,则此中断请求被丢弃;否则,则将其放入IRR中。

在此中断请求不能进行下一步处理之前,它一直被放在IRR中。一旦发现处理中断的时机已到,Priority Resolver将从所有被放置于IRR中的中断中挑选出一个优先级最高的中断,将其传递给CPU去处理。IR号越低的中断优先级别越高,比如IR0的优先级别是最高的。

8259A通过发送一个INTR(Interrupt Request)信号给CPU,通知CPU有一个中断到达。CPU收到这个信号后,会暂停执行下一条指令,然后发送一个INTA(Interrupt Acknowledge)信号给8259A。8259A收到这个信号之后,马上将ISR中对应此中断请求的Bit设置,同时IRR中相应的bit会被reset。比如,如果当前的中断请求是IR3的话,那么ISR中的bit-3就会被设置,IRR中IR3对应的bit就会被reset。这表示此中断请求正在被CPU处理,而不是正在等待CPU处理。

随后,CPU会再次发送一个INTA信号给8259A,要求它告诉CPU此中断请求的中断向量是什么,这是一个从0到255的一个数。8259A根据被设置的起始向量号(起始向量号通过中断控制字ICW2被初始化)加上中断请求号计算出中断向量号,并将其放置在Data Bus上。比如被初始化的起始向量号为8,当前的中断请求为IR3,则计算出的中断向量为8+3=11。

CPU从Data Bus上得到这个中断向量之后,就去IDT中找到相应的中断服务程序ISR,并调用它。如果8259A的End of Interrupt (EOI)通知被设定位人工模式,那么当ISR处理完该处理的事情之后,应该发送一个EOI给8259A。

8259A得到EOI通知之后,ISR寄存器中对应于此中断请求的Bit会被Reset。

如果8259A的End of Interrupt (EOI)通知被设定位自动模式,那么在第2个INTA信号收到后,8259A ISR寄存器中对应于此中断请求的Bit就会被Reset。

在此期间,如果又有新的中断请求到达,并被放置于IRR中,如果这些新的中断请求中有比在ISR寄存中放置的所有中断优先级别还高的话,那么这些高优先级别的中断请求将会被马上按照上述过程进行处理;否则,这些中断将会被放在IRR中,直到ISR中高优先级别的中断被处理结束,也就是说知道ISR寄存器中高优先级别的bit被Reset为止。

键盘的工作原理

键盘中有一个叫做键盘编码器的芯片,其通常是8048及其兼容芯片,当按下键盘的一个键的时候,键盘编码器会产生一个扫描码,之后将这个扫描码传递给主板中的8042键盘控制器,8042将其扫描码转换成相应的scan code set,并将其存放入缓冲区中,然后8042告诉8259产生了中断,转到相应的中断处理程序,如果此时又有别的键被按下,8042将不再接收扫描码,直到缓冲区中的扫描码被读走。

2.3.6 运行截图

2.4 系统调用

2.4.1 函数说明

printf.c

  1. #include "type.h"
  2. #include "const.h"
  3. int printf(const char *fmt, ...)
  4. {
  5. int i;
  6. char buf[256];
  7. va_list arg = (va_list)((char*)(&fmt) + 4);/* 4 是参数 fmt 所占堆栈中的大小 */
  8. i = vsprintf(buf, fmt, arg);//打印字符长度
  9. write(buf, i); //系统调用write
  10. return i;
  11. }

vsprintf.c

  1. #include "string.h"
  2. int vsprintf(char *buf, const char *fmt, va_list args)
  3. {
  4. char* p;
  5. char tmp[256];
  6. va_list p_next_arg = args;
  7. for (p=buf;*fmt;fmt++) {
  8. if (*fmt != '%') {
  9. *p++ = *fmt;
  10. continue;
  11. }
  12. fmt++;
  13. switch (*fmt) {
  14. case 'x':
  15. itoa(tmp, *((int*)p_next_arg));
  16. strcpy(p, tmp); //定义在string.h中
  17. p_next_arg += 4;
  18. p += strlen(tmp);
  19. break;
  20. case 's':
  21. break;
  22. default:
  23. break;
  24. }
  25. }
  26. return (p - buf);
  27. }

2.4.2 增加一个系统调用

步骤 内容 文件
1 NR_SYS_CALL加一 const.h
2 为sys_call_table[]加一个成员 globe.c
3 sys_write的函数体 tty.c
4 sys_write的函数声明 proto.h
5 write的函数声明 proto.h
6 _NR_write的定义 syscall.asm
7 write的函数体 syscall.asm
8 添加global write syscall.asm
9 如果参数个数和以前的系统调用相比有所增加,需要修改sys_call kernel.asm

2.4.3 调用流程

2.4.4 运行截图

最后实现的是当程序检测到回车键的时候,屏幕输出“hahahaahah”字样的字符串。

附录 调试bochs

安装bochs

Mac系统上安装bochs请参考

http://blog.csdn.net/yzr1183739890/article/details/54864841

Linux上安装请参考

(安装过程会碰到很多的错误,但是百度都能解决)

http://os.51cto.com/art/201407/446838_all.htm

运行

  1. bochs 2.6.9

进入”支持保护模式的简单操作系统的设计与实现14221091蔡杰”文件夹内,直接执行

  1. bochs -f bochsrc.bxrc
  2. bochs 2.4

需要修改配置文件bochsrc.bxrc如下:

  1. # how much memory the emulated machine will have
  2. megs: 32
  3. # filename of ROM images
  4. romimage: file=$BXSHARE/BIOS-bochs-latest
  5. vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
  6. # what disk images will be used
  7. floppya: 1_44=TINIX.IMG, status=inserted
  8. # choose the boot disk.
  9. boot: a
  10. # where do we send log messages?
  11. log: bochsout.txt
  12. # disable the mouse, since Tinix is text only
  13. mouse: enabled=0
  14. # enable key mapping, using US layout as default.
  15. keyboard_mapping: enabled=0, map=$BXSHARE/keymaps/x11-pc-us.map

之后再执行:

  1. bochs -f bochsrc.bxrc

即可。

修改

以下操作在”源程序”文件夹下修改:

  • 如果添加新文件需要修改MakeFile文件

  • 暂时支持在Linux上修改

  • 在terminal中依次执行以下指令即可:

  1. make clean
  2. make
  3. make buildimg
上传的附件 cloud_download 支持实模式保护模式跳转以及系统调用的操作系统的实现.zip ( 8.80mb, 1次下载 )
error_outline 下载需要8点积分

发送私信

如果我想抱你,你会伸手还是后退

21
文章数
14
评论数
最近文章
eject