在上两期中(自己动手写操作系统)我向大家讲述了如何使用Linux提供的开发工具在软盘的启动扇区写一些代码以及如何调用BIOS的问题现在这个操作系统已经越来越接近当年Linus Torvalds的那个具有历史意义的Linux内核了因此要马上把这个系统切换到保护模式之下 什么是保护模式 自从年推出第一个微处理器以来Intel处理器就在不断地更新换代从到奔腾奔腾Ⅱ奔腾等其体系结构也在不断变化以后提供了一些新的功能弥补了的一些缺陷这其中包括内存保护多任务及使用KB以上的内存等并仍然保持和家族的兼容性也就是说仍然具备了和的所有功能但是在功能上有了很大的增强早期的处理器是工作在实模式之下的以后引入了保护模式而在以后保护模式又进行了很大的改进在中保护模式为程序员提供了更好的保护提供了更多的内存事实上保护模式的目的不是为了保护程序而是要保护程序以外的所有程序(包括操作系统) 简言之保护模式是处理器的一种最自然的模式在这种模式下处理器的所有指令及体系结构的所有特色都是可用的并且能够达到最高的性能 保护模式和实模式 从表面上看保护模式和实模式并没有太大的区别二者都使用了内存段中断和设备驱动来处理硬件但二者有很多不同之处我们知道在实模式中内存被划分成段每个段的大小为KB而这样的段地址可以用位来表示内存段的处理是通过和段寄存器相关联的内部机制来处理的这些段寄存器(CSDSSS和ES)的内容形成了物理地址的一部分具体来说最终的物理地址是由位的段地址和位的段内偏移地址组成的用公式表示为 物理地址=左移位的段地址+偏移地址 在保护模式下段是通过一系列被称之为描述符表的表所定义的段寄存器存储的是指向这些表的指针用于定义内存段的表有两种全局描述符表(GDT)和局部描述符表(LDT)GDT是一个段描述符数组其中包含所有应用程序都可以使用的基本描述符在实模式中段长是固定的(为KB)而在保护模式中段长是可变的其最大可达GBLDT也是段描述符的一个数组与GDT不同LDT是一个段其中存放的是局部的不需要全局共享的段描述符每一个操作系统都必须定义一个GDT而每一个正在运行的任务都会有一个相应的LDT每一个描述符的长度是个字节格式如图所示当段寄存器被加载的时候段基地址就会从相应的表入口获得描述符的内容会被存储在一个程序员不可见的影像寄存器(shadow register)之中以便下一次同一个段可以使用该信息而不用每次都到表中提取物理地址由位或者位的偏移加上影像寄存器中的基址组成实模式和保护模式的不同可以从图和图中很清楚地看出来 图 实模式的寻址 图 保护模式下的寻址 图 段描述俯的格式 此外还有一个中断描述符表(IDT)这些中断描述符会告诉处理器到那里可以找到中断处理程序和实模式一样每一个中断都有一个入口但是这些入口的格式却完全不同因为在切换到保护模式的过程中没有使用到IDT所以在此就不多做介绍了 进入保护模式 有个位控制寄存器名字分别为CRCRCR和CRCR是保留在未来处理器中使用的在中没有定义CR包含系统的控制标志用于控制处理器的操作模式和状态CR和CR是用于控制分页机制的在此我们关注的是CR寄存器的PE位控制它负责实模式和保护模式之间的切换当PE=时说明处理器运行于保护模式之下其采用的段机制和前面所述的相应内容对应如果PE=那么处理器就工作在实模式之下 切换到保护模式实际就是把PE位置为为了把系统切换到保护模式还要做一些其它的事情程序必须要对系统的段寄存器和控制寄存器进行初始化把PE位置后还要执行跳转指令过程简述如下 创建GDT表; 通过置PE位为进入保护模式; 执行跳转以清除在实模式下读取的任何指令 下面使用代码来实现这个切换过程 需要的东西 ◆ 一张空白软盘 ◆ NASM编译器 下面是整个程序的源代码 org xc; 起始地址是:c jmp short begin_boot ; 跳过其它的数据跳转到引导程序的开始处 bootmesg db Our OS boot sector loading pm_mesgdb Switching to protected mode dw ; 每一扇区的字节数 db ; 每一簇的扇区数 dw ; 保留的扇区号 db dw xe dw xb db xf dw dw dw ; 读写扇区号 dw ; 隐藏扇区号 print_mesg : mov ahx; 使用中断h的功能在屏幕上写一个字符串 mov alx; 决定调用函数后光标所处的位置 mov bxx; 设置显示属性 mov cxx; 在此字符串长度为 mov dxx; 光标的起始行和列 int x; 调用BIOS的中断h ret; 返回调用程序 get_key : mov ahx int x; Get_key使用中断h的功能读取下一个字符 ret clrscr : mov axx; 使用中断h的功能实现卷屏如果al=则清屏 mov cxx; 清屏 mov dxxf; 卷屏至 mov bh; 使用颜色来填充 int x; 调用h中断 ret begin_boot : call clrscr ; 先清屏 mov bpbootmesg; 提供串地址 call print_mesg; 输出信息 call get_key; 等待用户按下任一键 bits call clrscr; 清屏 mov axxb; 使gs指向显示内存 mov gsax; 在实模式下显示一个棕色的A mov word [gs:]x; 显示 call get_key; 调用Get_key等待用户按下任一键 mov bppm_mesg;设置串指针 call print_mesg; 调用print_mesg子程序 call get_key; 等待按键 call clrscr; 清屏 cli; 关中断 lgdt[gdtr]; 加载GDT mov eaxcr or alx; 设置保护模式位 mov creax; 将更改后的字送至控制寄存器中 jmp codesel:go_pm bits go_pm : mov axdatasel mov dsax; 初始化ds和es使其指向数据段 mov esax mov axvideosel; 初始化gs使其指向显示内存 mov gsax mov word [gs:]x ; 在保护模式下显示一个白色的字符A spin : jmp spin; 循环 bits gdtr : dw gdt_endgdt; gdt的长度 dd gdt; gdt的物理地址 gdt nullsel equ $gdt; $指向当前位置所以nullsel = h gdt; 空描述符 dd dd ; 所有的段描述符都是位的 codesel equ $gdt; 这是h也就是gdt的第二个描述符 code_gdt dw xffff; 段描述符的界限是Gb dw x db x db xa db xcf db x datasel equ $gdt data_gdt dw xffff dw x db x db x db xcf db x videosel equ $gdt dw dw x; 基址是xb db xb db x db x db x gdt_end times ($$$)db dw xaa 把上面的代码存在一个名为abcasm的文件之中使用命令nasm abcasm将得出一个名为abc的文件然后插入软盘输入命令dd if=abc of=/dev/fd该命令将把文件abc写入到软盘的第一扇区之中然后重新启动系统就会看到如下的信息 *Our os booting * A (棕色) * Switching to protected mode * A (白色) 对代码的解释 上面给出了所有的代码下面我对上述代码做一些解释 ◆ 使用的函数 下面是代码中一些函数的说明 print_mesg 该子程序使用了BIOS中断h的功能h即向屏幕写一字符串属性控制是通过向一些寄存器中送入不同的值来实现的中断h是用于各种字符串操作我们把子功能号h送到ah中用于指明要打印一个字符串al寄存器中的说明了光标返回的起始位置表示调用函数后光标返回到下一行的行首如果al为则表示光标位于最后一个字符处 显存被分成了几页在同一时 |