DOS DPMI下的硬件中断机制
文章目录
本文旨在探讨DPMI(DOS Protect Mode Interface)环境下的硬件中断机制,以及在此环境下硬件中断的编写方法,本文并不探讨实模式下和保护模式下硬件中断的原理,但文中难免涉及一些这方面的知识,请读者自行阅读相关资料解决。在本文的最后将举一个硬件中断的编写实例。本文主要讨论的环境为:DOS6.22 + DJGPP + CWSDPMI。CWSDPMI为DJGPP自带的DPMI服务,也是一个好用的且免费的DPMI,有关这个环境的搭建,请参阅我以前的博文。本文是我2008年的作品,2023年重新整理发布,仅为存档,其中的程序并没有再次验证,特此说明。
1、DPMI下的硬件中断机理
实模式下的硬件中断有很多书籍中都有介绍,保护模式下的硬件中断要复杂一些,但也有一些资料。本博客中的大部分文章将介绍在DOS环境下使用DJGPP完成保护模式下的程序,编译出来的程序需要在DPMI的支持下运行在保护模式下,通常情况下,并没有什么,当你需要调用DOS下的实模式下的功能调用时,DPMI会帮助你切换到实模式,完成后再返回保护模式,基本上不需要编程者去做任何处理事情,但在处理硬件中断方面就比较微妙了,我们究竟应该在实模式下完成硬件中断还是在保护模式下,如果原有的硬件中断工作在实模式下,而我却编了一个保护模式下的硬件中断程序,按规则,在我的中断例程完成后,我必须再执行原有的硬件中断,显然这里有些问题。
首先,不论是在实模式下还是在保护模式下,硬件中断都会产生,当程序运行在DPMI下时,尽管你的程序是32位的,运行在保护模式下,但当你调用实模式下DOS的功能调用时,DPMI会切换到实模式下,可能一个硬件中断恰恰在这个时候产生了,但不管什么时候发生了硬件中断,只要你的程序运行在DPMI下,DPMI将截获硬件中断,并将首先传送给保护模式下的硬件中断程序执行,然后再转到实模式下的中断例程;但并不是所有的DPMI都将在两种模式下执行中断例程,比如我们本文讨论的环境,CWSDPMI下,DPMI截获中断并交给保护模式下的中断例程,只有在保护模式的中断例程不存在的情况下,DPMI才会去执行实模式下的中断例程,在CWSDPMI下,DPMI只执行一种模式下的中断例程;但WINDOWS 95下的DPMI就不一样,windows宣称会执行把两种模式下的中断例程都执行一遍(抱歉,我并没有验证这一说法);所以,一定要了解你所处的环境,在下面的叙述中,如果不特别说明,均只在CWSDPMI下,综上所述,在DPMI下,你可以只编写一个保护模式下的中断例程,而无须管实模式下的中断例程。
但是,我们毕竟是在DOS下编程,很多资源来自于实模式的DOS,所以如果你的中断程序中需要大量地调用DOS的资源时,你必须要考虑编写实模式的中断程序,否则在你的中断程序中DPMI将不断地在实模式和保护模式之间切换,这将十分麻烦。
2、DPMI下编写硬件中断的方法
保护模式下的中断程序,按下面方法设置。
-
用汇编语言编写的中断程序,可以按照下面的步骤进行设置:
- 取得原有的中断程序结构
调用__dpmi_get_protected_mode_interrupt_vector函数,并把返回值存在结构中,以便程序退出时可以恢复原来的中断程序
- 锁定内存
调用__dpmi_lock_linear_region函数锁定在中断程序中可能用到的数据变量,代码,调用的函数;锁定失败有可能造成你的程序崩溃;
- 设置新的中断程序
调用__dpmi_set_protected_mode_interrupt_vector,把新中断程序的选择子(selector)和偏移(offset)填入__dpmi_paddr中,传给函数,其中,pm_selector可以通过函数__dpmi_cs()获得
- 取得原有的中断程序结构
-
如果中断程序用C语言写成,可以按照下面方法设置:
- 取得原有的中断程序结构
调用_go32_dpmi_get_protected_mode_interrupt_vector,将返回值存入适当的结构中,以便程序退出时恢复原有的中断程序
- 对中断程序进行再包装
调用_go32_dpmi_allocate_iret_wrapper函数,按照要求传递参数,该函数将按照中断程序的规范建立一个小的汇编函数对你的C语言写的中断程序进行包装,该函数填充的结构_go32_dpmi_seginfo将作为下面函数调用的参数。
- 设置新的中断程序
调用_go32_dpmi_set_protected_mode_interrupt_vector函数,把上一步骤返回的_go32_dpmi_seginfo作为参数传给该函数。
- 链接原有中断
如果需要在执行完你的中断程序后执行原有的中断程序,不必执行前面两个步骤,直接调用_go32_dpmi_chain_protected_mode_interrupt_vector即可,就是说不用调用_go32_dpmi_allocate_iret_wrapper和_go32_dpmi_set_protected_mode_interrupt_vector这两个函数,_go32_dpmi_chain_protected_mode_interrupt_vector将完成所有的工作,在填结构_go32_dpmi_seginfo时,把中断程序的偏移放到pm_offset中,把_my_cs()的返回值放到pm_selector中即可;用这种方式设置的中断你只能通过DJGPP的退出代码进行释放,别无他法。
- 取得原有的中断程序结构
用C语言写中断程序的主要问题是你根本没有办法完全锁定你的中断程序所用到的存储器,所以,这种方法只适用于那些较小的程序,你可以确认你的程序不会被page到磁盘上去;这一点要特别引起注意,一般情况下,中断程序尽量使用汇编语言完成比较好。
3、DPMI下一个时钟中断的实例
-
老实说,没有什么很好的例子可以给大家演示中断程序,本来想结合前面博文中的AC97的内容编写一个PCI的中断例子,但复杂一些,涉及的问题也比较多,好在比较幸运的是我找到了一个int 8h的例子,个人感觉写的比较简单,好懂,对这段代码做了大量的修改,原程序接管了int 8h,我修改后的程序接管了int 8h和int 70h两个中断,我觉得这样更能说明问题。
-
int 8h(IRQ 0)是时钟中断,来自可编程的时钟芯片8254,现在的机器中已经没有了这颗芯片,给集成到了别的芯片中,不过保留了原来的I/O端口及8254的控制方法,所以仍然可以使用控制8254的方法控制时钟;要说明这个例子,我们不得不先简单介绍一下8254这颗芯片。
-
8254定时器这颗芯片有三个定时通道,每个通道有一个I/O地址,timer0 - timer2对应的I/O地址为40h - 42h,这个I/O通道是8位的,通过这个8位的I/O通道可以对8254进行编程
-
8254内部有一个16位的锁存寄存器,编程时送入的计数值首先进入这个锁存寄存器,然后被复制到计数寄存器,当接到8254上的时钟信号每来一个脉冲时,这个计数寄存器中的数值将减一,当减到0时,8254将输出一个脉冲,同时再次将锁存寄存器中的内容复制到计数寄存器中,开始下一轮的计数。
-
所以,8254芯片的每个通道有两个输入信号:一个是时钟信号,一个是门控信号,用于控制该通道工作或者不工作(后面我们会提到);一个输出信号,即计数完毕后的输出信号。
-
一般情况下,timer0用于产生每秒18.2次的系统时钟中断(int 8h),这就是我们下面例子中要编的中断;timer1用于存储器刷新(现在应该不用了?);timer2连接扬声器,可以产生方波使扬声器发声。这里说到的知识可能有点老,尤其是timer1的用法,不过不妨碍我们的例子。
-
8254还有一个控制寄存器,I/O地址为43h,8位通道,该寄存器的定义如下:
-
对这个控制寄存器,还是简单说明一下:
- bit [6:7]
准备对那个定时器通道进行操作,timer0 or timer1 or timer2
- bit [4:5]
1 2 3 4 5
读/写当前计数值 00:读当前计数值,先读低8位,后读高8位,要两次in指令 01:只写高8位,一次out指令 10:只写低8位,一次out指令 11:先写低8位,后写高8位,要两次out指令
- bit [1:3]
工作模式,有6种工作模式,其中模式0(000)为计数结束后产生中断;模式3(003)为方波频率发声器,这两种模式后面要用
- bit 0
写入值的格式,0–二进制格式,1–BCD码格式
- bit [6:7]
-
前面说到,8254的输入信号里还有一个门控信号,对于timer0和timer1,这个信号永远是选通的,但timer2的门控信号是可控的,由I/O端口61h的bit 0和bit 1控制,该I/O端口的bit0为1则关断timer2的门控信号,为0开启门控信号,bit1和可以控制timer2和扬声器的通断,我们不需要使用这个信号。
-
pc机种还有一个实时时钟中断,int 70h(IRQ 8),由以前的MC146818这颗芯片产生,现在这颗芯片也已经集成到其他芯片中了,不过操作和控制方法还是相同,MC146818可以产生多种类型的中断,在初始状态都是关闭的,我们仅用它的周期中断,缺省为每秒1024次,我们并没有更改任何参数,仅仅是在中断中增加了一个中断计数,我们希望以此中断来检验我们接管的int 8h的正确性。
-
好了,基本知识介绍完了。下面是程序清单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
001 #include <stdio.h> 002 #include <dos.h> 003 #include <string.h> 004 #include <dpmi.h> 005 #include <pc.h> 006 void pm_new8h(void); 007 void lock_pm_new8h(void); 008 void pm_new70h(void); 009 void lock_pm_new70h(void); 010 char *get_cmostime(void); 011 #define IRQ0 0x8 // 8254 time interrupt 012 #define IRQ8 0x70 // Real time interrupt 013 #define PIT0 0x40 // 8254 timer0 014 #define PITMODE 0x43 // 8254 control word /* 8254 controll word bit 7:6 timer number 00--timer0 01--timer1 10--timer2 bit 5:4 latch,read format; 00--latch current count; 01--write low byte(no latching); 10--write high byte(no latching); 11--write low,then high byte bit 3:1 mode number; 000--interrupt on terminal count; 001--programmable one-shot; 010--rate generator 011--sequare wave generator; 100--software triggered strobe; 101--hardware triggered strobe bit 0 count byte; 0--binary 1--bcd */ 015 #define IMR0 0x21 // IMR of 8259-0 016 #define IMR1 0xa1 // IMR of 8259-1 017 #define RTC_ADDR 0x70 018 #define RTC_DATA 0x71 019 #define PITCONST 1193180L // Frequence of 8254 020 #define PIT0DEF 18.2067597 // ticks per sec of previous int 021 #define RT_MS_PER_TICK 0.9765625 022 #define NEW8H 1 // flag. new int 8h valiable. 023 static float tick_per_ms = 0.0182068; // ticks per ms 024 static float ms_per_tick = 54.9246551; // interval between two ticks 025 static float freq8h = 18.2067597; // frequency of system timer 026 static unsigned char flag8h = 0; 027 static _go32_dpmi_seginfo old_handler_8h, new_handler_8h; 028 static _go32_dpmi_seginfo old_handler_70h, new_handler_70h; 029 static _go32_dpmi_registers r; 030 unsigned long int ticks_8h = 0; 031 unsigned long int ticks_70h = 0; /*********************************************** * New int 8h initialization * It chains int routine of protected mode and * real mode ***********************************************/ 032 void pctimer_init(unsigned int Hz) { 033 unsigned int pit0_set, pit0_value; 034 if (flag8h != NEW8H) { // Current int 8h is old. 035 disable(); // Disable interrupts 036 lock_pm_new8h(); // Lock int routine in protected mode 037 lock_pm_new70h(); // Lock int routine in protected mode // Lock some var that will be used in int routine 038 _go32_dpmi_lock_data(&ticks_8h, sizeof(ticks_8h)); 039 _go32_dpmi_lock_data(&ticks_70h, sizeof(ticks_70h)); 040 _go32_dpmi_lock_data(&r, sizeof(r)); // Get the previous int routine in protected mode // and chain a new routine 041 _go32_dpmi_get_protected_mode_interrupt_vector(IRQ0, &old_handler_8h); 042 new_handler_8h.pm_offset = (int)pm_new8h; 043 new_handler_8h.pm_selector = _go32_my_cs(); 044 _go32_dpmi_chain_protected_mode_interrupt_vector(IRQ0, &new_handler_8h); 045 _go32_dpmi_get_protected_mode_interrupt_vector(IRQ8, &old_handler_70h); 046 new_handler_70h.pm_offset = (int)pm_new70h; 047 new_handler_70h.pm_selector = _go32_my_cs(); 048 _go32_dpmi_allocate_iret_wrapper(&new_handler_70h); 049 _go32_dpmi_set_protected_mode_interrupt_vector(IRQ8, &new_handler_70h); // initial RTC 050 outportb(RTC_ADDR, 0x0b); 051 outportb(RTC_DATA, 0x42); // initial 8259-2 052 outportb(IMR1, 0x0c); // initial Timer0 of 8254 053 outportb(PITMODE, 0x36); // 8254 command register // 00110110b.timer0,write low and high // sequare wave generator,binary 054 pit0_value = PITCONST / Hz; 055 pit0_set = (pit0_value & 0x00ff); 056 outportb (PIT0, pit0_set); 057 pit0_set = (pit0_value >> 8); 058 outportb (PIT0, pit0_set); 059 // initial vars for int 060 ticks_8h = 0; 061 flag8h = NEW8H; // new int 8h 062 freq8h = Hz; // frequence of new int 063 tick_per_ms = freq8h / 1000; // ticks per ms 064 ms_per_tick = 1000 / freq8h; // ms of per tick 065 enable(); // enable interrupts 066 } 067 } 068 void pctimer_exit(void) { 069 unsigned int pit0_set, pit0_value; 070 unsigned long tick; 071 char *cmostime; 072 if (flag8h == NEW8H) { 073 disable(); 074 outportb(PITMODE, 0x36); 075 outportb(PIT0, 0x00); 076 outportb(PIT0, 0x00); 077 outportb(RTC_ADDR, 0x0b); 078 outportb(RTC_DATA, 0x02); 079 outportb(IMR1, 0x0d); 080 _go32_dpmi_set_protected_mode_interrupt_vector(IRQ0, &old_handler_8h); 081 _go32_dpmi_free_iret_wrapper(&new_handler_70h); 082 _go32_dpmi_set_protected_mode_interrupt_vector(IRQ8, &old_handler_70h); 083 enable(); 084 cmostime = get_cmostime(); 085 tick = PIT0DEF * ( (((float) *cmostime) * 3600) + (((float) *(cmostime + 1)) * 60) + (((float) *(cmostime + 2))) ); 086 biostime(1, tick); 087 flag8h = 0; 088 freq8h = PIT0DEF; 089 tick_per_ms = freq8h / 1000; 090 ms_per_tick = 1000 / freq8h; 091 } 092 } /**************************************** * New int 8h routine in protected mode ****************************************/ 093 void pm_new8h(void) { 094 ticks_8h++; 095 } /************************************************ * Lock the memory of new int 8h routine region * in protected mode ************************************************/ 096 void lock_pm_new8h(void) { 097 _go32_dpmi_lock_code(pm_new8h, (unsigned long)(lock_pm_new8h - pm_new8h)); 098 } /**************************************** * New int 70h routine in protected mode ****************************************/ 099 void pm_new70h(void) { 100 disable(); 101 ticks_70h++; 102 outportb(RTC_ADDR, 0x0c); 103 inportb(RTC_DATA); 104 outportb(0xa0, 0x20); 105 outportb(0x20, 0x20); 106 enable(); 107 } /************************************************ * Lock the memory of new int 70h routine region * in protected mode ************************************************/ 108 void lock_pm_new70h(void) { 109 _go32_dpmi_lock_code(pm_new70h, (unsigned long)(lock_pm_new70h - pm_new70h)); 110 } /***************************************** * Sleep delayms ms *****************************************/ 111 void pctimer_sleep(unsigned int delayms) { 112 unsigned long int delaytick; 113 delaytick = delayms * tick_per_ms + ticks_8h; 114 do { 115 } while (ticks_8h <= delaytick); 116 } 117 char *get_cmostime(void) { 118 char *buff; 119 static char buffer[6]; 120 char ch; 121 buff = buffer; 122 memset(&r, 0, sizeof (r)); 123 r.h.ah = 0x02; 124 _go32_dpmi_simulate_int(0x1a, &r); 125 ch = r.h.ch; 126 buffer[0] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10); 127 ch = r.h.cl; 128 buffer[1] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10); 129 ch = r.h.dh; 130 buffer[2] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10); 131 buffer[3] = r.h.dl; 132 buffer[4] = (char)(r.x.flags & 0x0001); 133 buffer[5] = 0x00; 134 return(buff); 135 } // Main program 136 int main(void) { 137 int i; 138 unsigned long int start, finish; 139 int elapsedtime[20]; 140 float average_error, sum = 0; 141 printf("Press any key to begin the test. "); 142 printf("(It lasts for 20 seconds.)\n\n"); 143 getch(); 144 printf("Test in progress. Please wait...\n"); 145 pctimer_init(1000); // 1000Hz, 1000 ticks per sec 146 for (i = 0; i < 10; i++) { 147 start = ticks_70h; 148 pctimer_sleep(1000); 149 finish = ticks_70h; 150 elapsedtime[i] = (int)(finish - start); 151 } 152 pctimer_exit(); 153 printf("Test finished.\n"); 154 printf("Press any key to display the result...\n"); 155 getch(); 156 for (i = 0; i < 10; i++) { 157 sum += (float)((elapsedtime[i] - 1024) * RT_MS_PER_TICK); 158 printf("iteration:%2d expected:1024 observed:%d difference:%d\n", i + 1, elapsedtime[i], elapsedtime[i] - 1024); 159 } 160 average_error = (float)sum / 10; 161 printf("\ntotal error:%.2f ms average error:%.2f ms\n", sum, average_error); 162 return 0; 163 }
-
为了说明方便,源程序中加了行号,所有的注释语句没有行号。
-
先大概说一下这个程序完成的事情
PC机系统的时钟中断是18.2次/秒,本程序重新编写了系统时钟中断int 8h,试图做到1ms中断一次,链接到原中断上,这样int 8h(IRQ 0)的中断频率将从18.2Hz变成1000Hz,这势必影响到PC机的时钟,因为很多资源是依赖这个时钟中断的,比如时间;同时我们接管了实时时钟中断int 70h(IRQ 8),并启动了它的周期中断,中断频率应该是1024Hz,在这个中断中我们仅仅作了一个中断计数;主程序中在启动中断后做了10次的循环,每次循环前记录int 70h的中断次数计数值,然后等待int 8h中断1000次(应该刚好1秒钟),然后再记录int 70h的中断次数计数值,理论上说,int 8h中断1000次,int 70h应该刚好中断1024次,如果数值吻合,说明int 8h完全按照我们的预期在运行。
-
常量、变量说明:
- ticks_8h:int 8h的中断次数计数
- ticks_70h:int 70h的中断次数计数
- PIT0:8254 timer0的端口地址,我们要修改timer0的设置
- PITMODE:8254的控制寄存器
- IMR1:第二颗8259中断控制器的中断屏蔽寄存器地址,因为IRQ 8连在第2颗8259上
- RTC_ADDR:实时时钟芯片(MC146818)地址寄存器地址
- RTC_DATA:实时时钟芯片(MC146818)数据寄存器地址
-
主程序从136行开始,145行调用pctimer_init初始化两个硬件中断,传递的参数1000表明把int 8h的中断频率设成1000Hz;146行–151行是测试部分的主循环,前面有过介绍,pctimer_sleep(1000)可以等待int 8h中断1000次,最后把int 70h在此期间的中断次数记录在数组elapsedtime中,共后面显示结果用;循环完毕后,调用pctimer_exit恢复以前的中断,然后显示测试记录。
-
比较复杂的部分是函数pctimer_init,从32行–67行。
- 35行关中断
- 36、37行锁定了两个中断程序的代码
- 38–40行锁定了中断程序中要用到的数据
- 41–44行设置了int 8h(IRQ 0)
- 45–49行设置了int 70h(IRQ 8),注意这两个中断的设置方法是有所不同的,我们把int 8h与原来的中断链接到了一起,而int 70h没有链接原来的中断,读者可以通过这段代码体会其中的不同
- 50、51行设置了实时时钟芯片的状态寄存器B,目的是开启周期中断,读者可以参考与实时时钟芯片相关的资料
- 52行重新设置了第二颗8259中断控制器,目的是清除对IRQ 8的屏蔽,这里要注意的是,在我做测试的机器上,我读取了第一颗8259的中断屏蔽寄存器,知道其用于级联第二颗8259的IRQ 2并没有被屏蔽,所以我并没有设置第一颗8259的中断屏蔽寄存器,如果你的机器与我的不同,请注意设置第一颗8259的中断屏蔽寄存器,打开IRQ 2,另外,我知道我的机器第二颗8259的中断屏蔽寄存器的缺省设置值是0dh,刚好IRQ 8被屏蔽了,所以我直接送了一个0ch到第二颗8259的中断屏蔽寄存器,以打开IRQ 8,在你的机器上可能也有些不同,请按实际情况设置,只要打开IRQ 8即可,不要改变其他的屏蔽位,以免麻烦,由于此处并非本文的重点,所以在代码上没有作更多处理
- 54–58行初始化8254的timer0,请参考前面关于8254芯片的介绍部分,另外在14和15行之间的注释也有一个对8254的简单介绍
- 60–64行设置了一些变量,明白变量的含义就好了,源程序中有注释
- 65行打开中断,此时两个新的中断开始运行了
-
93–95行是int 8h的中断程序,没有什么好说的,只是不断递增ticks_8h这个变量,注意int 8h这个中断程序是要链接到原有中断上的,所以最后不需要向8259发出中断结束的指令
-
99–107行是int 70h的中断程序,需要做一些说明,首先102、103行读取了实时时钟芯片的状态寄存器C,这是必须要做的,否则芯片认为你没有响应前一个中断,不会发出下一个中断;另外,这个中断和int 8h不同,没有链接到原有中断,所以你必须向8259发出中断完成的指令,又因为IRQ 8连接在第二颗8259上,而第二颗8259通过第一颗8259级联,所以必须向两颗8259均发出中断完成的指令,这就是104、105行的作用
-
117–135行这段程序对本文不重要,这是因为我们改变了系统时钟中断(int 8h)的中断频率,这势必导致本机时间混乱,所以在退出时,读取以下实时时钟芯片中的实时时间,然后重新设置一下时间
-
68–92行这段程序基本上是pctimer_init这个函数的逆动作,也不需要过多说明
-
至此,程序解释完了,希望能给你一些帮助。
欢迎访问我的博客:https://whowin.cn
email: hengch@163.com
文章作者 whowin
上次更新 2008-05-07