深入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 的东东,在这里正好复习一下。

相关文档
最新文档