深入80386 保护模式编程之学习笔记
深入80386 保护模式编程之学习笔记
Zhang hui
Shanghai
2006-10-11
BLOG:https://www.360docs.net/doc/a13154275.html,/blog.asp?name=amzhang
Email:norwegianzh@https://www.360docs.net/doc/a13154275.html,
序
以前看了很多本关于80386保护模式下编程的书,网上也搜集了很多保护模式相关的电子文档,看得时候似乎看懂了,可以时间一长就发现概念很模糊!正巧在学习linux的时候,在赵炯博士的网站上找到了一个关于保护模式的虚拟平台(bochs+dos).这个平台虚拟dos环境,里面有16个循序渐进的例子是学习保护模式很好的教程。理论+实践是最佳的学习方式,为了加深记忆,所以才有了这个笔记,希望对当然也希望能够对其他人有所帮助。由于本人水平有限,笔记中不免有错误之处,欢迎来信指出!
注:笔记中所提到的商标
简介
在开始笔记之前,先介绍一下CPU 吧 1978年,Intel 首次生产出16位的微处理器,并命名为i8086,8086采用HMOS 工艺制造,芯片有2.9万个晶体管,5v 单一电源 40pin 双列直插,时钟是5-10Mhz 。8086有16根数据线,20根地址线。由于使用的是40pin 封装所以一部分引脚分时复用。1982年Intel 推出了划时代的最新产品80286芯片,该芯片比8006和8088都有了飞跃的发展,虽然它仍旧是16位结构,但是在CPU 的内部含有13.4万个晶体管,时钟频率由最初的6MHz 逐步提高
到20MHz 。其内部和外部数据总线皆为16位,地址总线24位,可寻址16MB 内存。1985年INTEL 推出了80386芯片,它是80X86系列中的第一种32位微处理器,而且制造工艺也有了很大的进步,与80286相比,80386内部内含27.5万个晶体管,时钟频率为12.5MHz ,后提高到20MHz ,25MHz ,33MHz 。80386的内部和外部数据总线都是32位,地址总线也是32位,可寻址高达4GB 内存。它除具有实模式和保护模式外,还增加了一种叫虚拟V86的工作方式,可以通过同时模拟多个8086处理器来提供多任务 能力。除了标准的80386芯片,也就是我们以前经常说的80386DX 外,出于不同的市场和应用考虑,INTEL 又陆续推出了一些其它类型的80386 芯片:80386SX 、80386SL 、80386DL 等。1988年推出的80386SX 是市场定位在80286和80386DX 之间的一种芯片,其与 80386DX 的不同在于外部数据总线和地址总线皆与80286相同,分别是16位和24位(即寻址能力为16MB)。1990年推出的80386 SL 和80386 DL 都是低功耗、节能型芯片,主要用于便携机和节能型台式机。80386 SL 与80386 DL 的不同在于前者是基于80386SX 的,后者是基于80386DX 的,但两者皆增加了一种新的工作方式:系统管理方式(SMM)。当进入系统管理方式后,CP 就自动降低运行速度、控制显示屏和硬盘等其它部件暂停工作,甚至停止运行,进入“休眠”状态,以达到节能目的。386之后的cpu 我们就不再介绍了,接下来开始我们的例子了。 需要软件:bochs tc2.1 masm5
一. 尝试跳入保护模式
图:8086
1.工具:bochs ,tc
2.1, masm5 ,dos
2.预备知识
1) CR0控制寄存器,这个32位的控制寄存器保存和控制机器的状态。
MSW
80386仅仅定义了6个控制和状态位:
PG:页管理使能位。置PG=1,使能80386内部分页机能,清0不使用分页机制。
PE:保护模式允许位。PE置位,CPU进入保护模式方式,清零处于实模式方式。
ET:扩展类型位。ET置位表示系统中配置了80386协处理器,适用32位协处理器协议,否则使用16位协处理器协议。EM=0时ET位失效
TS:任务切换位。当任务切换时,处理器将把TS置1。
MP:监控协处理器位。MP=1,表示有协处理器工作,否则协处理器没有工作。
EM:模拟协处理器位。EM=1,表示使用软件仿真协处理器。
2)BIOS数据区
0040:006CH 这是BIOS作为时钟计数器的一个双字单元,时钟步进一次,此值增加一次,大约每秒钟发出18.2个脉冲值(这个脉冲和8253有关)。其值为0时,表示一天开始(午夜),当此计数器达到一天结束的值时,计数器清0,且字节0040:0070H置1。中断1AH功能调用可从此双字单元中读取一天的时间。
3)汇编和C混合编程约定
①在汇编里要声明该函数可以被外部调用如public _read_msw,_read_cr0,_write_cr0这里声明了3个函数,这里要注意的是函数远调用(far),近调用(near).如果使用远调用声明函数那么在C程序中使用关键字里声明该文件要使用far 标志符。还要使用关键字“extern”对函数作显式说明。例子中我把_read_msw特意声明为远调用,其他都是近调用。
②由于C编译器产生的所有标号都以下划线(_)为前缀,而C程序在调用汇编程序时要求汇编程序名也以下划线( _ )为前缀。
③参数传递顺序是按其在参数表中出现的顺序的反序被压入堆栈中,即第一个参数最后进入堆栈,它在栈中的地址最低。汇编程序取C 的参数。远过程返回地址占四个字节,BP 压入占二字节,所以第一个参数在BP+6所指向的单元。对于近过程第一个参数在BP+4所指向的单元。例如_write_cr0中由于是近调用所以第一个参数在BP+4所指向的单元。
④每种C 数据类型都有一个标准的返回位置,一般在AX 中(极小、小、中模式),DX :AX(紧凑、大、巨模式),如:char,unsigned char,enum,short int 等,返回值位置为AX ,且返回数据必须放置在RET 指令之前。16位值返回到AX 中;32位值返回到DX:AX 中,其中DX 存放高16位值,而AX 存放低16位值.
3. 代码
汇编代码 ;pm.asm
.model small, c .386p ;使用386特权指令 .code
public _read_msw, _read_cr0, _write_cr0 ;声明函数,供C 调用 _read_msw proc far ;读CR0前16位(0-15) smsw ax ;返回值写入AX ret _read_msw endp _read_cr0 proc near ;读CR0 mov eax, cr0 ;读 CR0 到 EAX mov edx, eax
shr edx, 16 ; DX:AX = CR0 (return value) ret _read_cr0 endp _write_cr0 proc near ;写CR0 push bp ;保存BP mov bp, sp mov eax, [bp+4] ; eax = 32-bit parameter
Func(I,j,k)
2 4 6 8
mov cr0, eax ;
pop bp
ret
_write_cr0 endp
End
C代码
#include
#include
#include
#include
#include "pm.h"
#define byte unsigned char
#define word unsigned int
/*读cmos值,地址由reg指定*/
byte read_CMOS_reg (byte reg)
{
outportb (0x70, reg);
return inportb (0x71);
}
/*写cmos值,地址由reg指定*/
void write_CMOS_reg (byte reg, byte value)
{
outportb (0x70, reg);
outportb (0x71, value);
}
/*延时程序 cmos 0字节存放的是秒*/
void delay_RTC (int secs)
{
byte x;
while (secs--)
{
x = read_CMOS_reg(0); /* read seconds from RTC */ while (read_CMOS_reg(0) == x); /* wait for the next value */ };
}
void my_exit() {
printf ("\nWe're back to real mode...\n");
}
int main() {
unsigned long far *BIOS_timer = MK_FP (0x40, 0x6C);
clrscr();
printf ("Welcome to the 1st PMode tutorial!\n\n");
if (read_msw() & 1) /*读CR0,并且判断PE是否置位*/
{
printf ("The CPU is already in PMode. \n Aborting...");
return 0;
}
printf ("We're going to PMode using CR0 for 5 seconds...\n");
/*屏蔽可屏蔽中断,我们只是进入保护模式,中断没有配置所以要屏蔽否则会引起异常 */ disable();
/* 屏蔽NMIs */
outportb (0x70, inportb(0x70) | 0x80);
/* 先把CR0读进来,PE置位,然后写回去,呵呵我们就进入了保护模式 */
write_cr0 (read_cr0() | 1L);
/*延时5秒这个函数运行在保护模式 */
delay_RTC (5);
/* CR0 -PE位清零退出保护模式 */
write_cr0 (read_cr0() & 0xFFFFFFFEL);
*BIOS_timer += 91L; /*恢复bios时间数据区5*18.2 ticks total */
/* 使能NMIs */
outportb (0x70, inportb(0x70) & 0x7F);
/* 使能中断 */
enable();
my_exit();
return 0;
}
4.运行截图
5.总结
这是个很简单的例子,只是跳入保护模式等待5秒钟然后跳出来。但是还是有些细节问题要注意的,首先由于我们没有配置保护模式中断系统,所以所有的中断包括NMI都要屏蔽掉当然你也可以尝试一下如果不屏蔽出现什么结果。其次进入保护模式很多C的库函数都不
能使用,如果你想在保护模式下调用printf,肯定会当机.的。例子中包含很多IBM PC legacy 的东东,在这里正好复习一下。