摘 要:在对硬件编程时,往往要涉及到I/O操作,然而介绍这方面的资料较少,本文介绍基于Windows环境下编写I/O程序的两个方法。作为一个I/O程序的例子,本文还介绍了如何读取网卡的全局唯一物理地址以及如何用程序清除CMOS密码的方法,这对于许多编程人员来说都具有一定的参考价值。
关键字:I/O操作、虚拟设备驱动程序、嵌入式汇编,数据端口。
我们在对硬件编程时,往往要涉及到I/O操作,通常的方法是编写VxD(虚拟设备驱动程序),由于VxD工作在Windows的ring0层,故可以对I/O口进行相当自由的操作,但是对许多人来说编写VxD是一件相当痛苦的事,这一点对于刚进入Windows编程的人来说尤为如此。此外,编写VxD极易破坏操作系统,不到迫不得已尽量不用。实际上,要访问I/O口,除了一些特殊的情况,如监视某一个I/O口的工作状态,一般情况下并不一定要编写专门的I/OVxD,这是因为 Windows并没有对应用程序所在的ring3层进行I/O屏蔽,故我们可以使用VC++的I/O函数或使用嵌入式汇编的方法来访问I/O口(有的C++编译器没有相应的I/O函数,可用嵌入式汇编来访问I/O口)。
⑴ 使用VC++的I/O函数访问I/O口
VC++的I/O函数共有六个:
int _inp( unsigned short port );
该函数从port指定的端口号中读取一个8位的字节,返回值即为所读的字节。
unsigned short _inpw( unsigned short port );
该函数从port指定的端口号中读取一个16位的字,返回值即为所读的字。
unsigned long _inpd( unsigned short port );
该函数从port指定的端口号中读取一个32位的双字,返回值即为所读的双字。
int _outp( unsigned short port, int databyte );
该函数向port指定的端口号中写一个字节databyte,返回值为所写的字节。
unsigned short _outpw( unsigned short port, unsigned short dataword );
该函数向port指定的端口号中写一个字dataword,返回值为所写的字,
unsigned long _outpd( unsigned short port, unsigned long dataword );
该函数向port指定的端口号中写一个双字dataword,返回值为所写的双字。
使用这六个必须包含头文件conio.h。
⑵ 使用嵌入汇编访问I/O口
由于C语言的嵌入汇编功能,使得我们可以使用汇编语言的in和out指令来进行I/O操作,其典型程序如下:
从I/O口中读一个字节:
int in_p(unsigned short port)
{
unsigned char result;
_asm
{
mov dx,port;
in al,dx;
mov result,al;
}
return result;
}
从I/O口中读一个字:
int in_pw(unsigned short port)
{
unsigned short result;
_asm
{
mov dx,port;
in ax,dx;
mov result,ax;
}
return result;
}
从I/O口中读一个双字:
unsigned long in_pd( unsigned short port)
{
unsigned long result;
_asm
{
mov dx,port;
in eax,dx;
mov result,eax;
}
return result;
}
向I/O口中写一个字节:
int out_p(unsigned short port,unsigned char databyte)
{
unsigned char result;
_asm
{
mov dx,port;
mov al,databyte
out dx,al;
mov result,al;
}
return result;
}
向I/O口中写一个字:
int out_pw(unsigned short port,unsigned short dataword)
{
unsigned short result;
_asm
{
mov dx,port;
mov ax,dataword;
out dx,ax;
mov result,ax;
}
return result;
}
向I/O口中写一个双字:
unsigned long out_pd( unsigned short port, unsigned long dataword )
{
unsigned long result;
_asm
{
mov dx,port;
mov eax,dataword;
out dx,eax;
mov result,eax;
}
return result;
}
下面举几个例子来说明它们的应用:
例1:网卡MAC地址的读取。
由于网卡的唯一物理地址是存储在网卡内部存储空间0000H-000aH的地址范围内,故只需启动远程DMA传输,将其依次送往数据端口即可。其步骤如下:
1. 保存网卡命令寄存器的值(port=300H)。
2. 选择0页寄存器(port=300H)。
3. 设置DMA传输方式(port=30eH)。
4. 设置DMA传输起始地址(port=308H,309H)。
5. 设置传输字节(port=30aH,30bH)。
6. 启动远程DMA读操作(port=300H)。
7. 从数据端口依次读出数据(port=310H)。
8. 恢复命令寄存器的值(port=300H)。
int GetMACAddress(unsigned char* address)
{
int port;
int var;
port=0x300;
int bak=in_p(port); //保存网卡命令寄存器的值;
port=0x300;
var=0x21;
out_p(port,var); //选择0页寄存器;
port=0x30e;
var=0x49;
out_p(port,var); //设置DMA传输方式;
var=0;
port=0x308;
out_p(port,var); //设置DMA传输起始地址低字节;
var=0;
port=0x309;
out_p(port,var); //设置DMA传输起始地址高字节;
var=0x14;
port=0x30a;
out_p(port,var); //设置传输字节数低字节;
var=0;
port=0x30b;
out_p(port,var); //设置传输字节数高字节;
var=0x0a;
port=0x300;
out_p(port,var); //启动远程DMA读操作;
port=0x310;
int i=0;
for(i=0;i<10;i++) //从数据端口依次读出数据;
address[i]=in_p(port);
bool end=false;
while(end==false)
{
port=0x307; //读中断标志判断DMA传输是否完成;
unsigned char k=(BYTE)in_p(port);
k=k&0x40;
if(k!=0)
end=true;
}
port=0x300;
out_p(port,bak); //恢复命令寄存器的值;
return i;
}
例2: 删除CMOS密码。
读CMOS的操作是通过70H、71H端口来进行的,从70H端口写入要读写单元的地址,再从71H端口读写相应的数据。其程序如下:
void ClearCmosPassword()
{
int port=0x70;
int var=0x21;
_outp(port,var);
port=0x71;
var=0x20;
_outp(port,var);
return;
}
|