采用VXD技术实现实的通信_接口电路论文
关键词:vxd 实时 串口通信
引言
在微软的视窗操作系统中,系统内核掌管所有的应用程序,通过独特的任务调度算法实现cpu的分时多任务处理方式。多任务处理对大多数用户可能是件好事,但是对那些想把实时通信建立在windows操作系统上的特殊用户来说,操作界面的图形化并不比ms-dos的单任务更具吸引力。在视窗操作系统里可以进行实时通信和控制码?答案是:vxd技术可以帮我们在获取友好的人机界面的同时还拥有很强的实时性。
1 vxd技术解析
vxd技术可追溯到windows3.1,它的引入就是要让操作系统实现多工以及硬件资源的共享。为了支持多个ms-dos任务同时执行,windows98让每个ms-dos应用程序在各自的虚拟机(vm)上运行,各自互不相干;而所有的widnows应用程序却都在一个虚拟机上运行。图1所示的结构框图很好地说明了windows98的整体架构。
图1中,由众多的vxd组成系统级代码处于最底层。其中,处于中心地位的是一名为vmm32的vxd,它负责协调和管理所有的vxds。其它vxds则通过消息机制(这个消息机制由vmm32.vxd来维护)彼此联系。由所有vxds开放出的服务接口(api)组成了一个服务网,它们彼此通过合作的方式,提供windows98的系统底层驱动服务。
从以上windows98系统架构可以看出,要想在视窗平台下获取很强的实时性,仅靠提升应用程序线程优先级的方法是不够的。因为win32应用程序代码属于ring3级,而vxd代码则属于ring0级;采用vxd撰写的实时通信程序可以完全不受代码限制,可以直接对硬件进行操作。vxd的这个特点正是实时通信建立所必须的。
设计实时通信的vxd前,先解释以下几个问题:
①vmm32使用vpicd.vxd虚拟化每个硬件和软件中断。vmm32为每个虚拟机(vm)维护一个idt结构,当中断发生时,cpu先保护中断现场,然后经由当前vm的idt把这个中断引导至相应的中断处理程式。
中断的虚拟化,使我们有机会给每个中断提供新的中断处理函数,并可以让多个硬件共享同一个中断号。vpicd.vxd为我们提供这些服务。
②vmm有两个调度器,用以在多个线程和vms之间实现抢占式多工。主调度器负责选定下一个将被执行的线程。这个选择可以是一个,也可以是多个。然后,主调度器把选择结果送给所谓的时间片调度器,并由后者完成各个应用程序间的时间片分配。调度器也时应用程序经由呼叫win32线程优先调整api(如setthreadpriority和setpriorityclass等)做出回应。当中断发生时,vmm32自动提升中断处理函数所在vm之优先级,保证中断处理函数能及时被执行。
③vxd和win32应用程序可直接通信。win32应用程序可通过一个系统api(devicelocontrol(…))来呼叫位于底层的vxd为其服务。在呼叫vxd前,首先必须调用creatfile(…)这个api加载该vxd(如果该vxd是一个静态vxd,则不用加载)。所有的呼叫动作其实都通过vmm32完成。vxd也可以通过消息方式和位于上层的win32应用程序通信。she11.vxd为所有希望以消息机制和win32应用程序通信的vxd提供了这一服务。
以上是编写一个串口通信驱动需要的系统层面知识。对于windows底层的了解。
2 用vxd实现一个实时串口通信驱动
接下来用vxd技术实现一个实时串行通信的驱动。这个vxd是一个动态(dynamic)vxd,当它的服务被呼叫时,vmm32会动态加载这个vxd。作者采用的工具是c+98ddk。当然也可以使用其它的工具,如masm6.11(或更高版本)、vtoolsd。用c搭配ddk完成vxd构建的好处是,可以使用c语言完成绝大部分的程序,程序比较容易阅读和维护。
用c来实现一个vxd驱动,需要准备如下条件:一个.asm的汇编语言接口文件(在其中定义vxd要处理的系统消息和输出api),一个.c的函数实现文件(在其中完成自己函数实体),一个.def的定义文件(在其中定义vxd中各个段的别名并汇成一个ddb)和一个.mak档(用来编译并连接生成vxd,可有可无)。在这里,仅给出用c实现的函数档。至于其它的文件,可以从本文所列的参考书目或其它文献中找到相关文档的说明。
这个串口通信驱动程序的功能是:实时送出一个byte的数据,实时接收一个byte的数据。作为演示之用,并没有加入其它代码。该vxd驱动主要由如下3个系统消息(由vmm32来维护和管理)处理函数组成,其代码如下:
(1)onsysdynamicdeviceinit()函数
bool onsysdynamicdeviceinit()
{ //onsysdynamicdeviceinit
irqhandle=vpicd_virtualize_irq((dword)(&irq4));
if(irqhandle= =0){
return false;
}
return true; //onsysdynamicdeviceinit
}
该函数用来完成vxd初始化所做的工作。在本例中,由于实时监视串口中断的需要,要给com1的中断安装一个自定义的断服务函数。98ddk已经提供了这个函数的c语言版,其原型是hirq static vpicd_virtualize_irq(pvid pvid),在vpicd.h中。该函数需要一个指针作为参数(指向名为vpicd_irq_descriptor的结构体),函数传回一个指向该虚拟irq的句柄(该句柄在后来的vpicd服务中需要提供)。vpicd_irq_descriptor结构体的组成为:
typedef struct vpicd_irq_descriptor{
ushort vid_irq_number; //irq号(0~15)
ushort vid_options; //标志位选项
ulong vid_hw_int_proc; //硬件中断服务程序的地址
ulong vid_virt_int_proc; //虚拟中断服务程序
ulong vid_mask_change_proc //mask change调用例程
ulong vid_iret_proc; //iret调用例程
ulong vid_iret_time_out; //在vm的进程优先级提升之前的最大等待时间
ulong vid_hw_int_ref; //硬件中断服务程序的数据存放地址
}vid;
其中只用到三位。在本例中需要声明一个名为irq4的全局变量为vid结构,并付给如下初值:vid irq4={4,0,hwproc,0,0,0,0,500,0},表示将要虚拟化irq4,改变其中断处理函数为void hwproc(void),该函数的原型如下:
void hwproc(void){
_asm{
mov dx,0x3f8
in al,dx
mov byte ptr [readin],al
clc
}
return;
}
在这个中断处理中,仅仅从com1的数据寄存器(地址为3f8h)中读取接收到的数值,并把该数值存放在一个类型为byte、名为readin的内存中。
(2)onsysdynamicdeviceexit()函数
bool onsysdynamicdeviceexit()
{
vpicd_force_default_behavior(irqhandle);
//解除irq4虚拟化
return true;
} //onsysdynamicdeviceexit
该数提供了用于善后处理vxd在卸载时需要完成的事件。在本例中,和vxd初始化对应,需要解除对com1的中断irq4的虚拟化。作者也是用98ddk在vpicd.h中提供的外包函数void static_inline vpicd_force_default_behavior(hirq hirp)。该函数唯一需要的参数便是使用vpicd_virtualize_irq函数传回的irq句柄。
(3)ondeviceiocontrol()函数
dword ondeviceiocontrol(pdiocparameters p){
switch (p->dwiocontrolcode)
{
case 1: //端口写功能
if(!p->lpvoutbuffer||p->cboutbuffer<1)
{ //输出缓存的有效性检查
return error_invalid_parameter;
}
if(serial_out((dword)(p->lpvinbuffer)))
{ //数据发送
*(byte*)(p->lpvoutbuffer)=*(byte*)(p->lpvinbuffer);
}
else{
*(byte*)(p->lpvoutbuffer)=0;
}
open_int(); //打开com1中断
return 0;
case 2: //端口读功能
if(*(byte*)reading= =0x00)
{ //数据读入
*(byte*)(p->lpvoutbuffer)=0x00;
return 0;
}
*(btye*)(p->lpvoutbuffer)=*(byte*)(readin);
return 0;
}
return 0;
}
return 0;
}
ondeviceiocontrol函数用来处理win32应用程序对vxd的呼叫。win32应用程序的呼叫会让vmm32送给该vxd一个系统,并传递进一个diocparameters结构的指针。该结构里包含win32应用程序呼叫时传递进来的各个参数。这个结构的组成如下:
typedef stunct diocparams{
dword internall; //指向客户寄存器的指针
dword vmhande; //该vm的句柄
dword internal2; //指向ddb结构的指针
dword dwioconrolcode; //deviceiocontrol例程中呼叫的控制码
dwod lpvinbuffer; //deviceiocontrol例程呼叫所传递进来的输入缓冲区地址
dword cbinbuffer; //输入缓冲区的大小
dword lpvoutbuffer; //deviceiocontrol例程呼叫所传递进来的输出缓冲区地址
dword cboutbuffer; //输出缓冲区的大小
dword lpcbbytesreturned; //拷贝到输出缓冲区中的字节数(可以为null)
dword lpoverlapped; //deviceiocontrol例程呼叫所传递进来的重叠i/o块结构
dword hdevice; //ring3层呼叫应用程序句柄
dword tagprocess; //例程标签
}
dioparameters;
其中,dwiocontrolcode指明了win32应用程序需要vxd提供的哪一项服务。在本例中采用一个switch-case语句作为服务入口,如下所示。其中服务1为让串口送出一个字节,服务2为读取一个已经由串口接收的字节。函数open_int()是用来初始化串口以便接收字节数据;函数bool serial_out(dword pbuffer)是让串口发出一个字节。它们的函数体分别如下:
bool serial_out(dword pbuffer){
if(pbuffer= =null){
return false;
}
_asm {
pushfd
cli
push eax
push edx
mov dx,0x3fb ;设置com1的波特率
mov al,0x83
out dx,al
mov dx,0x3f8
mov al,12
out dx,al
mov dx,0x3f9
mov al,0
out dx,al
mov dx,0x3fb ;设置com1的线控项
mov al,3
out dx,al
mov dx,0x3f9 ;cmm1关中断
mov al,0
out dx,al
mov dx,0x3fa ;关闭com1的fifo功能
mov
[1]