ASPI编程详解:从Windows到Win32
1. ASPI for Windows基础
在ASPI for Windows环境中,我们可以使用
SendASPICommand
函数来执行各种SCSI相关操作。以
SC_RESET_DEV
命令为例,它用于向指定目标发送SCSI总线设备重置信号。以下是
SRB_BusDeviceReset
结构体的定义:
typedef struct
{
BYTE SRB_Cmd; // ASPI command code = SC_RESET_DEV
BYTE SRB_Status; // ASPI command status byte
BYTE SRB_HaId; // ASPI host adapter number
BYTE SRB_Flags; // ASPI request flags
DWORD SRB_Hdr_Rsvd; // Reserved, MUST = 0
BYTE SRB_Target; // Target's SCSI ID
BYTE SRB_Lun; // Target's LUN number
BYTE SRB_ResetRsvd1[14]; // Reserved, MUST = 0
BYTE SRB_HaStat; // Host Adapter Status
BYTE SRB_TargStat; // Target Status
FARPROC SRB_PostProc; // Post routine
BYTE SRB_ResetRsvd2[34]; // Reserved, MUST = 0
} SRB_BusDeviceReset
该结构体各成员的详细说明如下表所示:
| 成员 | 描述 | 读写属性 |
| ---- | ---- | ---- |
| SRB_Cmd | 必须包含
SC_RESET_DEV
| 写 |
| SRB_Status | 返回时,此域与下面定义的返回状态相同 | 读 |
| SRB_HaId | 指定请求针对的已安装主机适配器,编号从0开始 | 写 |
| SRB_Flags | 此函数目前保留,传递给ASPI管理器之前必须置零 | 写 |
| SRB_Hdr_Rsvd | 此DWORD字段目前保留,传递给ASPI管理器之前必须置零 | - |
| SRB_Target | 设备的目标ID | 写 |
| SRB_Lun | 设备的逻辑单元号(LUN),Windows版ASPI忽略该字段 | 写 |
| SRB_HaStat | SCSI命令完成后,ASPI管理器将设置此域为以下主机适配器状态 | 读 |
| SRB_TargStat | SCSI命令完成后,ASPI管理器将设置此域为以下目标状态 | 读 |
| SRB_PostProc | 如果启用了发布功能,Windows版ASPI将把ASPI请求的完成信息发布到此函数指针 | 写 |
其中,
SRB_HaStat
和
SRB_TargStat
的具体状态值及含义如下:
-
SRB_HaStat状态值
:
| 值 | 含义 |
| ---- | ---- |
| HASTAT_OK | 主机适配器未检测到错误 |
| HASTAT_SEL_TO | 选择超时 |
| HASTAT_DO_DU | 数据溢出/不足 |
| HASTAT_BUS_FREE | 意外的总线空闲 |
| HASTAT_PHASE_ERR | 目标总线相位序列失败 |
-
SRB_TargStat状态值
:
| 值 | 含义 |
| ---- | ---- |
| STATUS_GOOD | 无目标状态 |
| STATUS_CHKCOND | 检查状态(感应数据在SenseArea中) |
| STATUS_BUSY | 指定的目标/LUN繁忙 |
| STATUS_RESCONF | 预留冲突 |
SendASPICommand
函数的返回值指定了函数的执行结果,可能的返回值如下表所示:
| 值 | 含义 |
| ---- | ---- |
| SS_COMP | SCSI/ASPI请求已无错误完成 |
| SS_INVALID_HA | 无效的主机适配器编号 |
| SS_INVALID_SRB | SCSI请求块(SRB)有一个或多个参数设置不正确 |
| SS_ASPI_IS_BUSY | ASPI管理器此时无法处理请求,通常是资源已耗尽,应稍后重试 |
以下是一个向主机适配器#0、目标#5发送SCSI总线设备重置命令的示例代码:
SRB_BusDeviceReset MySRB;
WORD ASPI_Status;
MySRB.SRB_Cmd = SC_RESET_DEV;
MySRB.SRB_HaId = 0;
MySRB.SRB_Flags = 0;
MySRB.SRB_Hdr_Rsvd = 0;
MySRB.SRB_Target = 5;
MySRB.SRB_Lun = 0;
ASPI_Status = SendASPICommand ( (LPSRB) &MySRB );
// Make sure all other reserved fields are zeroed
// before passing the SRB to ASPI for Windows
while (MySRB.SRB_Status==SS_PENDING);
2. ASPI通知机制
在发送ASPI for Windows SCSI请求后,有两种方式可以得知SCSI请求是否完成:
-
轮询(Polling)
:这是最简单的方法。发送命令后,当ASPI for Windows将控制权返回给程序时,可以通过轮询状态字节来等待命令完成。例如,以下代码段向目标#2发送SCSI查询命令:
SRB_ExecSCSICmd6 MySRB;
char InquiryBuffer[32];
// Code is entered with 'MySRB' zeroed.
MySRB.SRB_Cmd = SC_EXEC_SCSI_CMD;
MySRB.SRB_Flags = SRB_DIR_SCSI;
MySRB.SRB_Target = 2;
MySRB.SRB_BufLen = 32;
MySRB.SRB_SenseLen = SENSE_LEN;
MySRB.SRB_BufPointer = InquiryBuffer;
MySRB.SRB_CDBLen = 6;
MySRB.CDBByte[0] = SCSI_INQUIRY;
MySRB.CDBByte[4] = 32;
SendASPICommand ( (LPSRB) &MySRB ); // Send Inquiry command
while ( MySRB.SRB_Status == SS_PENDING ); // Wait till it's finished
// At this point, the SCSI command has completed
// with or without an error.
if ( MySRB.SRB_Status == SS_COMP )
; // Command completed without error
else
; // Command completed with error
需要注意的是,由于Windows目前是非抢占式多任务操作系统,使用轮询时要谨慎,上述示例在释放处理器资源和设置超时处理方面表现不佳。
-
发布(Posting)
:大多数应用程序会使用发布机制来获知SCSI请求的完成情况。启用发布功能后,ASPI for Windows会通过调用回调函数来通知完成情况。以下代码段在
WM_CREATE消息期间向目标#2发送SCSI查询命令:
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG);
void FAR PASCAL ASPIPostProc ( LPSRB );
HWND PostHWND;
HANDLE hInstance;
//**********************************************************************
// ASPIPostProc - ASPI for Windows will post completion of a SCSI
// request to this function. Note that this is most
// likely during interrupt time so you can only use
// a few Windows functions like 'PostMessage.' This
// example post procedure is very simple. It will
// wake up your application by posting a WM_ASPIPOST
// message to your window handle.
//**********************************************************************
void FAR PASCAL ASPIPostProc ( LPSRB DoneSRB )
{
PostMessage (PostHWND, WM_ASPIPOST,
(WORD) ((SRB_ExecSCSICmd6 far *)DoneSRB)->SRB_Status,
(DWORD) DoneSRB );
return;
}
//***********************************************************************
// WndProc - Window message handler
//***********************************************************************
long FAR PASCAL WndProc ( HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
static SRB_ExecSCSICmd6 MySRB;
static char InquiryBuffer[32];
switch (message)
{
case WM_CREATE:
// Code is entered with 'MySRB' zeroed.
lpfnASPIPostProc = MakeProcInstance (ASPIPostProc, hInstance);
PostHWND = hwnd;
MySRB.SRB_Cmd = SC_EXEC_SCSI_CMD;
MySRB.SRB_Flags = SRB_DIR_SCSI|SRB_POSTING;
MySRB.SRB_Target = 2;
MySRB.SRB_BufLen = 32;
MySRB.SRB_SenseLen = SENSE_LEN;
MySRB.SRB_BufPointer = InquiryBuffer;
MySRB.SRB_CDBLen = 6;
ExecSRB.SRB_PostProc = lpfnASPIPostProc;
MySRB.CDBByte[0] = SCSI_INQUIRY;
MySRB.CDBByte[4] = 32;
if ( SendASPICommand ( (LPSRB) &MySRB ) != SS_PENDING )
{
;
// Check return status for cause of failure!
;
// Posting will NOT occur due to failure
}
else
{
;
// ASPI for Windows will post completion to
;
// 'lpfnASPIPostProc' when command has completed.
}
return 0;
case WM_ASPIPOST:
// Return status is in 'wParam'
// SRB Pointer is in 'lParam'
// We might want to send another ASPI request here.
// Look at 'ASPIPostProc' for more information.
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc ( hwnd, message, wParam, lParam );
}
当回调函数被调用时,
wParam
字段将包含ASPI命令的状态(
SRB_Status
),
lParam
字段将包含已完成的SRB的远指针。
3. 其他注意事项
在使用ASPI for Windows时,还需要注意以下几点:
- 程序不应在有未完成的SCSI I/O时退出,必要时可发送ASPI中止命令。
- SRB和数据缓冲区必须位于页面锁定内存中,建议使用
GlobalAlloc
分配缓冲区,然后依次使用
GlobalLock
和
GlobalPageLock
进行锁定。
- 所有ASPI for Windows管理器至少应支持64K(64千字节)或更小的传输。若请求的传输大小超出支持范围,
SendASPICommand
将返回
SS_BUFFER_TO_BIG
错误,此时应将传输拆分为64K或更小的传输。
- 发送SCSI命令时,要处理
SS_ASPI_IS_BUSY
返回状态,可稍后重试命令。
- 启用发布功能且返回值不等于
SS_PENDING
时,ASPI for Windows不会将完成信息发布到指定的窗口句柄。
- ASPI for Windows支持多任务,但每个ASPI请求应使用单独的SRB,且建议每个目标一次只发送一个SRB。
- 使用发布机制时,回调函数很可能在中断时间被调用,调用Windows例程时要谨慎,可调用
PostMessage
函数。
- 在将SRB传递给ASPI for Windows之前,务必将所有保留字段置零。
4. 错误代码和消息
所有ASPI for Windows调用都可能失败,以下是常见的错误代码及其含义:
| 错误代码 | 值 | 含义 |
| ---- | ---- | ---- |
| 0x0000 | SS_PENDING | SCSI请求正在进行中 |
| 0x0001 | SS_COMP | SCSI/ASPI请求已无错误完成 |
| 0x0002 | SS_ABORTED | SCSI命令已被中止 |
| 0x0004 | SS_ERR | SCSI命令已完成但有错误 |
| 0x0080 | SS_INVALID_CMD | 无效的ASPI命令代码 |
| 0x0081 | SS_INVALID_HA | 无效的主机适配器编号 |
| 0x0082 | SS_NO_DEVICE | SCSI设备未安装 |
| 0x00E0 | SS_INVALID_SRB | SCSI请求块(SRB)有一个或多个参数设置不正确 |
| 0x00E1 | SS_OLD_MANAGER | 加载了一个或多个不支持Windows的ASPI for DOS管理器 |
| 0x00E2 | SS_ILLEGAL_MODE | 此ASPI管理器不支持此Windows模式,通常在实模式下运行Windows时出现 |
| 0x00E3 | SS_NO_ASPI | 没有加载ASPI管理器,通常是由于DOS ASPI管理器未驻留内存 |
| 0x00E4 | SS_FAILED_INIT | 由于其他原因(非
SS_OLD_MANAGER
、
SS_ILLEGAL_MODE
或
SS_NO_ASPI
),ASPI for Windows无法正确初始化,可能是系统资源不足 |
| 0x00E5 | SS_ASPI_IS_BUSY | ASPI管理器此时无法处理请求,通常是资源已耗尽,应稍后重试 |
| 0x00E6 | SS_BUFFER_TO_BIG | ASPI管理器无法处理大于64K的传输,需将SCSI I/O拆分为更小的64K传输 |
ASPI编程详解:从Windows到Win32
5. ASPI for Win32概述
ASPI for Win32与ASPI for Windows有相似之处,但也有一些需要注意的问题:
-
动态链接库
:如果使用显式动态链接,ASPI for Win32的DLL名为
WNASPI32.DLL
,而不是
WINASPI.DLL
。使用隐式动态链接时,要使用
WNASPI32.LIB
代替
WINASPI.LIB
。
-
可重入性和异步I/O
:ASPI for Win32是完全可重入的,支持重叠的异步I/O。ASPI模块可以在其他请求未完成时发送额外的ASPI请求,但每个请求都要使用单独的SRB。
-
SRB结构定义
:ASPI for Win32的SRB结构定义与ASPI for Win16不同,但结构名称保持一致。若要在16位和32位应用程序中使用同一源代码库,需使用适当的包含文件进行条件编译。
-
数据传输方向
:对于需要数据传输的请求,SRB_Flags字段中的方向位必须正确设置,且不再是可选的。对于不需要数据传输的请求,方向位将被忽略。
-
缓冲区对齐
:缓冲区必须根据
SC_HA_INQUIRY
命令返回的缓冲区对齐掩码进行对齐,建议至少按双字对齐。
-
内存锁定
:ASPI for Win32中的ASPI SCSI请求块(SRB)和数据缓冲区不需要位于页面锁定内存中,ASPI管理器会处理缓冲区和SRB的锁定。
-
大传输处理
:如果
SendASPI32Command
返回
SS_BUFFER_TO_BIG
错误,应将传输拆分为多个64K字节或更小的传输,也可以使用
GetASPI32Buffer
和
FreeASPI32Buffer
调用分配大传输缓冲区。为了最大兼容性,建议不请求大于64K字节的传输大小。
-
发布机制
:如果启用了发布(回调)功能,无论请求状态如何,回调过程都会被调用,这与ASPI for DOS和ASPI for Win16不同。
-
CDB区域
:CDB区域的长度固定为16,因此感应数据区域不再像ASPI for Win16那样根据命令长度移动位置。如果开发仅针对Win32的应用程序,无需考虑“浮动”感应缓冲区。
-
设备扫描
:扫描设备时,
SendASPI32Command
可能在SRB_Status字段中返回
SS_NO_DEVICE
状态,除了检查主机适配器状态
HASTAT_SEL_TO
外,还需检查此异常。
6. 编程约定
ASPI for Win32规范中使用的主要数据类型如下表所示:
| 类型 | 大小(字节) | 描述 |
| ---- | ---- | ---- |
| VOID | N/A | 表示无返回值或无函数参数 |
| BYTE | 1 | 无符号8位值 |
| WORD | 2 | 无符号16位值 |
| DWORD | 4 | 无符号32位值 |
| LPVOID | 4 | 通用指针,用于需要函数指针或Win32句柄的SRB字段 |
| LPBYTE | 4 | 指向BYTE数组的指针,主要用作缓冲区指针 |
| LPSRB | 4 | 指向SRB_*结构之一的通用指针 |
除非另有说明,所有多字节字段遵循英特尔的字节顺序,即低字节在前,高字节在后。所有标记为保留的结构字段必须设置为零,并且结构必须进行字节对齐。不同编译器设置字节对齐的方式如下:
- Microsoft编译器:使用
#pragma pack(1)
。
- Borland编译器:使用
#pragma option -a1
。
所有ASPI for Win32函数都使用“C”调用约定(由Microsoft编译器实现为
__cdecl
)从
WNASPI32.DLL
导出。在这种调用约定下,调用者先将最后一个函数参数压入栈中,并且负责从栈中弹出参数。
7. 调用ASPI函数
使用ASPI for Win32的应用程序称为ASPI模块,它们通过
WNASPI32.DLL
与ASPI进行交互。
WNASPI32.DLL
有五个入口点,如下表所示:
| 入口点 | 描述 |
| ---- | ---- |
| GetASPI32SupportInfo | 初始化ASPI并返回基本配置信息 |
| SendASPI32Command | 提交SCSI请求块(SRB)供ASPI执行 |
| GetASPI32Buffer | 分配满足Win95/WinNT大传输要求的缓冲区 |
| FreeASPI32Buffer | 释放之前使用
GetASPI32Buffer
分配的缓冲区 |
| TranslateASPI32Address | 在Win95 DEVNODE和ASPI HA/ID/LUN地址三元组之间进行转换 |
其中,
GetASPI32Buffer
、
FreeASPI32Buffer
和
TranslateASPI32Address
这三个函数从EZ - SCSI的4.01版本开始成为ASPI for Win32的一部分。
要访问这五个函数,它们必须驻留在内存中。Windows 95和Windows NT通过动态链接将动态链接库(DLL)加载到内存中,并解析应用程序对这些DLL中函数的引用。动态链接有显式和隐式两种方式,显式动态链接是加载和调用ASPI for Win32的首选方法,它允许完全控制ASPI的加载时间和错误处理,也是检测三个新ASPI函数是否可用的唯一方法。
显式动态链接的操作步骤如下:
1. 使用
LoadLibrary
加载
WNASPI32.DLL
:
HINSTANCE hinstWNASPI32;
hinstWNASPI32 = LoadLibrary( "WNASPI32" );
if( !hinstWNASPI32 )
{
// Handle ASPI load error here. Usually this involves the display of an
// informative message based on the results of a call to GetLastError().
}
-
使用
GetProcAddress获取每个ASPI for Win32入口点的地址:
DWORD (*pfnGetASPI32SupportInfo)( void );
DWORD (*pfnSendASPI32Command)( LPSRB );
BOOL (*pfnGetASPI32Buffer)( PASPI32BUFF );
BOOL (*pfnFreeASPI32Buffer)( PASPI32BUFF );
BOOL (*pfnTranslateASPI32Address)( PDWORD, PDWORD );
pfnGetASPI32SupportInfo = GetProcAddress( hinstWNASPI32, "GetASPI32SupportInfo" );
pfnSendASPI32Command = GetProcAddress( hinstWNASPI32, "SendASPI32Command" );
pfnGetASPI32Buffer = GetProcAddress( hinstWNASPI32, "GetASPI32Buffer" );
pfnFreeASPI32Buffer = GetProcAddress( hinstWNASPI32, "FreeASPI32Buffer" );
pfnTranslateASPI32Address = GetProcAddress( hinstWNASPI32, "TranslateASPI32Address" );
-
检查函数地址是否有效:
-
如果使用的是旧版本的ASPI,最后三个函数地址将为
NULL,此时应禁用ASPI模块中所有新功能的使用。 -
同时,检查
pfnGetASPI32SupportInfo和pfnSendASPI32Command是否为NULL,如果为NULL,表示访问DLL时出错,应用程序应停止使用ASPI,并使用FreeLibrary卸载WNASPI32.DLL。
-
如果使用的是旧版本的ASPI,最后三个函数地址将为
- 使用获取的函数地址调用函数:
DWORD dwASPIStatus = pfnGetASPI32SupportInfo();
8. 总结
ASPI编程在Windows和Win32环境中有不同的特点和要求。在ASPI for Windows中,需要注意请求块的设置、通知机制的使用、内存锁定和错误处理等问题。而ASPI for Win32则在可重入性、异步I/O、SRB结构、数据传输方向、缓冲区对齐和发布机制等方面有了改进和变化。
在实际编程中,要根据具体的操作系统环境和应用需求选择合适的ASPI版本,并遵循相应的编程约定和注意事项。通过显式动态链接可以更好地控制ASPI的加载和使用,确保应用程序的稳定性和兼容性。
下面是一个简单的mermaid流程图,展示了ASPI for Win32显式动态链接的流程:
graph TD;
A[开始] --> B[LoadLibrary加载WNASPI32.DLL];
B --> C{加载成功?};
C -- 是 --> D[GetProcAddress获取函数地址];
C -- 否 --> E[处理加载错误];
D --> F{函数地址有效?};
F -- 是 --> G[使用函数地址调用函数];
F -- 否 --> H[禁用新功能或停止使用ASPI并卸载DLL];
G --> I[结束];
E --> I;
H --> I;
通过以上的介绍和示例代码,希望能帮助开发者更好地理解和使用ASPI编程,在不同的Windows环境中实现高效、稳定的SCSI请求处理。
超级会员免费看
51

被折叠的 条评论
为什么被折叠?



