ASPI编程概述
1. SRB超时与事件超时
在ASPI编程中,
WaitForSingleObject
使用了无限超时,因为SRB超时和事件超时是不同的。可以使用
SC_GETSET_TIMEOUT
将超时与SRB关联起来。以下是一个示例代码:
BYTE byInquiry[32];
DWORD dwASPIStatus;
HANDLE heventSRB;
SRB_ExecSCSICmd srbExec;
heventSRB = CreateEvent( NULL, TRUE, FALSE, NULL );
if( !heventSRB )
{
// Couldn't get manual reset event, put error handling code here!
}
memset( &srbExec, 0, sizeof(SRB_ExecSCSICmd) );
srbExec.SRB_Cmd = SC_EXEC_SCSI_CMD;
srbExec.SRB_Flags = SRB_DIR_IN | SRB_EVENT_NOTIFY;
srbExec.SRB_Target = 5;
srbExec.SRB_BufLen = 32;
srbExec.SRB_BufPointer = byInquiry;
srbExec.SRB_SenseLen = SENSE_LEN;
srbExec.SRB_CDBLen = 6;
srbExec.SRB_PostProc = (LPVOID)heventSRB;
srbExec.CDBByte[0] = SCSI_INQUIRY;
srbExec.CDBByte[4] = 32;
ResetEvent( hevenSRB );
dwASPIStatus = SendASPI32Command( (LPSRB)&srbExec );
if( dwASPIStatus == SS_PENDING )
{
WaitForSingleObject( heventSRB, INFINITE );
}
if( srbExec.SRB_Status != SS_COMP )
{
// Error processing the SRB, put error handling code here.
}
2. SC_ABORT_SRB命令
SendASPI32Command
函数使用命令代码
SC_ABORT_SRB
来请求中止一个挂起的SRB。如果应用程序希望停止某个未完成的I/O请求,就可以发出此命令,但该中止命令的成功与否无法保证。
SRB_Abort结构体定义如下:
typedef struct
{
BYTE SRB_Cmd;
// ASPI command code = SC_ABORT_SRB
BYTE SRB_Status;
// ASPI command status byte
BYTE SRB_HaId;
// ASPI host adapter number
BYTE SRB_Flags;
// Reserved, MUST = 0
DWORD SRB_Hdr_Rsvd;
// Reserved, MUST = 0
LPSRB SRB_ToAbort;
// Pointer to SRB to abort
}
SRB_Abort, *PSRB_Abort;
SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含
SC_ABORT_SRB (0x03)
|
| SRB_Status | 输出 | 此SRB是同步的,返回值与
SendASPI32Command
相同,可能为
SS_COMP
、
SS_INVALID_HA
或
SS_INVALID_SRB
。
SS_COMP
仅表示尝试中止SRB,而非已成功中止 |
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |
| SRB_ToAbort | 输入 | 指向要中止的SRB的指针,中止操作的实际结果由该SRB最终返回的状态指示 |
需要注意的是,
SC_ABORT_SRB
命令的成功性无法保证,在Win32下其用途大大降低,建议在新的ASPI模块中使用
SC_GETSET_TIMEOUTS
SRB来管理SRB超时。
3. SC_RESET_DEV命令
SendASPI32Command
函数使用命令代码
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;
// Reserved, MUST = 0
DWORD SRB_Hdr_Rsvd;
// Reserved, MUST = 0
BYTE SRB_Target;
// Target's SCSI ID
BYTE SRB_Lun;
// Target's LUN number
BYTE SRB_Rsvd1[12];
// Reserved, MUST = 0
BYTE SRB_HaStat;
// Host Adapter Status
BYTE SRB_TargStat;
// Target Status
LPVOID SRB_PostProc;
// Post routine
BYTE SRB_Rsvd2[36];
// Reserved, MUST = 0
}
SRB_BusDeviceReset, *PSRB_BusDeviceReset;
SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含
SC_RESET_DEV (0x04)
|
| SRB_Status | 输出 | 此SRB是异步的,完成后可能为
SS_COMP
或
SS_ERR
等。
SS_COMP
表示成功完成,
SS_ERR
需检查
SRB_HaStat
和
SRB_TargStat
字段 |
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |
| SRB_Target | 输入 | 目标设备的SCSI ID |
| SRB_Lun | 输入 | 目标设备的逻辑单元号,在Win32中此字段被忽略 |
| SRB_HaStat | 输出 | SCSI命令完成后,设置为主机适配器状态。仅当
SRB_Status
不为
SS_COMP
时有效 |
| SRB_TargStat | 输出 | SCSI命令完成后,设置为最终SCSI目标状态。仅当
SRB_Status
不为
SS_COMP
时有效 |
| SRB_PostProc | 输入 | 如果启用了发布(
SRB_POSTING
),包含指向函数的指针;如果启用了事件通知(
SRB_EVENT_NOTIFY
),包含事件句柄 |
主机适配器状态表如下:
| 符号 | 值 | 描述 |
| ---- | ---- | ---- |
| HASTAT_OK | 0x00 | 主机适配器未检测到错误 |
| HASTAT_TIMEOUT | 0x09 | 总线事务分配的时间用完 |
| HASTAT_COMMAND_TIMEOUT | 0x0B | SRB在等待处理时过期 |
| HASTAT_MESSAGE_REJECT | 0x0D | 处理SRB时收到MESSAGE REJECT |
| HASTAT_BUS_RESET | 0x0E | 检测到总线复位 |
| HASTAT_PARITY_ERROR | 0x0F | 检测到奇偶校验错误 |
| HASTAT_REQUEST_SENSE_FAILED | 0x10 | 目标设备报告检查条件后,适配器发出请求感测失败 |
| HASTAT_SEL_TO | 0x11 | 选择目标超时 |
| HASTAT_DO_DU | 0x12 | 数据溢出 |
| HASTAT_BUS_FREE | 0x13 | 意外的总线空闲 |
| HASTAT_PHASE_ERR | 0x14 | 目标总线相位序列失败 |
SCSI目标状态表如下:
| 符号 | 值 | 描述 |
| ---- | ---- | ---- |
| STATUS_GOOD | 0x00 | 无目标状态 |
| STATUS_CHKCOND | 0x02 | 检查状态(感测数据在SenseArea中) |
| STATUS_BUSY | 0x08 | 指定的目标/LUN繁忙 |
| STATUS_RESCONF | 0x18 | 预留冲突 |
需要注意的是,Windows 95和Windows NT操作系统目前不能正确处理总线设备复位,因此
SC_RESET_DEV
调用不能保证正常工作。
4. SC_GET_DISK_INFO命令
SendASPI32Command
函数使用命令代码
SC_GET_DISK_INFO
来获取磁盘类型SCSI设备的信息,包括BIOS Int 13h控制和设备可访问性、驱动器的Int 13h物理驱动器号以及Int 13h服务使用的驱动器几何信息。此命令在Windows NT中无效。
SRB_GetDiskInfo结构体定义如下:
typedef struct
{
BYTE SRB_Cmd;
// ASPI command code = SC_GET_DISK_INFO
BYTE SRB_Status;
// ASPI command status byte
BYTE SRB_HaId;
// ASPI host adapter number
BYTE SRB_Flags;
// Reserved
DWORD SRB_Hdr_Rsvd;
// Reserved
BYTE SRB_Target;
// Target's SCSI ID
BYTE SRB_Lun;
// Target's LUN number
BYTE SRB_DriveFlags;
// Driver flags
BYTE SRB_Int13HDriveInfo;
// Host Adapter Status
BYTE SRB_Heads;
// Preferred number of heads translation
BYTE SRB_Sectors;
// Preferred number of sectors translation
BYTE SRB_Rsvd1[10];
// Reserved
}
SRB_GetDiskInfo, *PSRB_GetDiskInfo;
SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含
SC_GET_DISK_INFO (0x06)
|
| SRB_Status | 输出 | 此SRB是同步的,返回值可能为
SS_COMP
、
SS_INVALID_HA
、
SS_NO_DEVICE
或
SS_INVALID_SRB
|
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |
| SRB_Target | 输入 | 目标设备的SCSI ID |
| SRB_Lun | 输入 | 目标设备的逻辑单元号 |
| SRB_DriveFlags | 输出 | 根据以下表格设置 |
| SRB_Int13DriveInfo | 输出 | 仅当
SRB_DriveFlags
为
DISK_INT13_AND_DOS
或
DISK_INT13
时有效,为Int 13h服务分配给设备的物理驱动器号 |
| SRB_Heads | 输出 | 仅当
SRB_DriveFlags
为
DISK_INT13_AND_DOS
或
DISK_INT13
时有效,为Int 13h服务使用的设备几何信息中的磁头数 |
| SRB_Sectors | 输出 | 仅当
SRB_DriveFlags
为
DISK_INT13_AND_DOS
或
DISK_INT13
时有效,为Int 13h服务使用的设备几何信息中的扇区数 |
驱动器标志表如下:
| 符号 | 值 | 描述 |
| ---- | ---- | ---- |
| DISK_NOT_INT13 | 0x00 | 设备不受Int 13h服务控制 |
| DISK_INT13_AND_DOS | 0x01 | 设备受Int 13h控制且被DOS占用 |
| DISK_INT13 | 0x02 | 设备受Int 13h控制但未被DOS占用 |
示例代码:
SRB_GetDiskInfo srbGetDiskInfo;
memset( &srbGetDiskInfo, 0, sizeof(SRB_GetDiskInfo) );
srbGetDiskInfo.SRB_Header.SRB_Cmd = SC_GET_DISK_INFO;
srbGetDiskInfo.SRB_Target = 2;
SendASPI32Command( (LPSRB)&srbGetDiskInfo );
if( srbGetDiskInfo.SRB_Status != SS_COMP )
{
// Error handling GetDiskInfo SRB. Error handling code goes here!
}
5. SC_RESCAN_SCSI_BUS命令
SendASPI32Command
函数使用命令代码
SC_RESCAN_SCSI_BUS
来重新扫描SRB中指定主机适配器编号的SCSI总线,会指示I/O子系统重新扫描SCSI总线并更新系统设备映射和ASPI管理器设备表。
SRB_RescanPort结构体定义如下:
typedef struct
{
BYTE SRB_Cmd;
// ASPI command code = SC_RESCAN_SCSI_BUS
BYTE SRB_Status;
// ASPI command status byte
BYTE SRB_HaId;
// ASPI host adapter number
BYTE SRB_Flags;
// Reserved, MUST = 0
DWORD SRB_Hdr_Rsvd;
// Reserved, MUST = 0
}
SRB_RescanPort, *PSRB_RescanPort;
SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含
SC_RESCAN_SCSI_BUS (0x07)
|
| SRB_Status | 输出 | 此SRB是同步的,返回值可能为
SS_COMP
或
SS_INVALID_HA
|
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |
在Windows NT下,I/O子系统不会重新扫描已知的设备/ID,只能检测新设备;在Windows 95下,重新扫描命令发出后,设备映射的更新可能会有延迟。
示例代码:
SRB_RescanPort srbRescanPort;
memset( &srbRescanPort, 0, sizeof(SRB_RescanPort) );
srbRescanPort.SRB_Cmd = SC_RESCAN_SCSI_BUS;
SendASPI32Command( (LPSRB)&srbRescanPort );
if( srbRescanPort.SRB_Status != SS_COMP )
{
// Error issuing port rescan. Error handling code goes here.
}
6. SC_GETSET_TIMEOUTS命令
SendASPI32Command
函数使用命令代码
SC_GETSET_TIMEOUTS
可以以半秒为增量设置特定目标的超时时间。设置后,该超时时间将应用于通过
SC_EXEC_SCSI_CMD
命令发送的所有SCSI命令。超时是针对进程的,不同应用程序可以为同一目标设置不同的超时时间。
SRB_HaId
、
SRB_Target
和
SRB_Lun
字段可以设置为通配符值,以便为多个目标设置超时时间。默认情况下,所有目标超时时间设置为30小时(最大允许值)。
SRB_GetSetTimeouts结构体定义如下:
typedef struct
{
BYTE SRB_Cmd;
// ASPI command code = SC_GETSET_TIMEOUTS
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
BYTE SRB_Target;
// Target's SCSI ID
BYTE SRB_Lun;
// Target's LUN number
DWORD SRB_Timeout;
// Timeout in half seconds
}
SRB_GetSetTimeouts, *PSRB_GetSetTimeouts;
SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含
SC_GETSET_TIMEOUTS (0x08)
|
| SRB_Status | 输出 | 此SRB是同步的,返回值可能为
SS_COMP
、
SS_INVALID_HA
、
SS_NO_DEVICE
或
SS_INVALID_SRB
|
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号。如果
SRB_DIR_OUT
在
SRB_Flags
中设置,则此值可以为通配符(0xFF),表示所有主机适配器上的
SRB_Target/SRB_Lun
组合都应设置新的超时时间 |
| SRB_Flags | 输入 | 可以设置为以下两个常量之一:
-
SRB_DIR_IN (0x08)
:用于检索当前超时设置,ASPI地址字段中不允许使用通配符
-
SRB_DIR_OUT (0x10)
:用于更改当前超时设置,ASPI地址字段中允许使用通配符 |
| SRB_Target | 输入 | 目标设备的SCSI ID。如果
SRB_DIR_OUT
在
SRB_Flags
中设置,则此值可以为通配符(0xFF),表示指定
SRB_HaId/SRB_Lun
的所有SCSI ID都应设置新的超时时间 |
| SRB_Lun | 输入 | 设备的逻辑单元号。如果
SRB_DIR_OUT
在
SRB_Flags
中设置,则此值可以为通配符(0xFF),表示指定
SRB_HaId/SRB_Target
的所有LUN都应设置新的超时时间 |
| SRB_Timeout | 输入 | 目标的超时时间(半秒)。如果是
SRB_DIR_OUT
,则此值为指定目标的新超时时间;如果是
SRB_DIR_IN
,则由ASPI设置为指定目标的当前超时时间。超时范围为0 - 108000(30小时),0表示最大超时(30小时) |
当为目标设置超时时间后,所有通过
SC_EXEC_SCSI_CMD
发送的SRB都将使用该超时时间。如果某个SRB超时,SCSI总线将被复位,所有正在执行的SRB将被取消,驱动程序会设置相应的错误代码。超时SRB的结果取决于驱动程序设置的错误代码,不同的控制器可能有不同的结果。使用事件通知和超时设置时,要注意
SRB_PostProc
字段中的
HEVENT
有独立的超时时间。
流程图如下:
graph TD;
A[开始] --> B[设置超时时间];
B --> C{SRB是否超时};
C -- 是 --> D[复位SCSI总线];
D --> E[取消所有SRB];
E --> F[设置错误代码];
F --> G[处理错误并重试];
C -- 否 --> H[继续执行];
H --> I[完成操作];
I --> J[结束];
通过以上介绍,我们对ASPI编程中的各种命令和超时设置有了更深入的了解。在实际应用中,需要根据具体需求选择合适的命令和设置,以确保程序的稳定性和可靠性。
ASPI编程概述(续)
7. 超时设置注意事项及示例
在使用
SC_GETSET_TIMEOUTS
命令设置超时时间时,除了上述提到的基本信息,还有一些细节需要注意。当使用通配符设置超时时间时,虽然可以方便地为多个目标统一设置,但要明确通配符是有特定作用范围的。例如,设置
HaId
为
0xFF
并不意味着
SRB_Target
和
SRB_Lun
可以随意取值,它们仍然需要根据具体情况合理设置。
以下是一个使用通配符设置超时时间的示例:
SRB_GetSetTimeouts srbGetSetTimeouts;
memset(&srbGetSetTimeouts, 0, sizeof(SRB_GetSetTimeouts));
srbGetSetTimeouts.SRB_Cmd = SC_GETSET_TIMEOUTS;
srbGetSetTimeouts.SRB_Flags = SRB_DIR_OUT;
srbGetSetTimeouts.SRB_HaId = 0xFF; // 通配所有主机适配器
srbGetSetTimeouts.SRB_Target = 0xFF; // 通配所有SCSI ID
srbGetSetTimeouts.SRB_Lun = 0xFF; // 通配所有LUN
srbGetSetTimeouts.SRB_Timeout = 100; // 设置超时时间为50秒(100个半秒)
DWORD dwASPIStatus = SendASPI32Command((LPSRB)&srbGetSetTimeouts);
if (dwASPIStatus != SS_COMP)
{
// 处理设置超时失败的情况
// Error handling code goes here!
}
8. 不同操作系统下的ASPI命令表现对比
不同操作系统对ASPI命令的支持和处理方式存在差异,这会影响到程序的兼容性和稳定性。以下是对Windows NT和Windows 95系统下部分ASPI命令的对比:
| 命令 | Windows NT表现 | Windows 95表现 |
| ---- | ---- | ---- |
| SC_RESCAN_SCSI_BUS | I/O子系统不会重新扫描已知的设备/ID,只能检测新设备,无法检测设备的移除或更换 | 重新扫描命令发出后,设备映射的更新可能会有延迟,最好结合即插即用消息和
TranslateASPI32Address
处理,或在命令发出5 - 10秒后自行刷新 |
| SC_RESET_DEV | 操作系统目前不能正确处理总线设备复位,调用不能保证正常工作 | 同样不能保证
SC_RESET_DEV
调用正常工作,该命令主要用于兼容从Win16移植的旧代码 |
9. 错误处理流程
在ASPI编程中,错误处理是确保程序健壮性的重要环节。不同的命令在执行过程中可能会出现各种错误,需要根据
SRB_Status
、
SRB_HaStat
和
SRB_TargStat
等字段的值进行相应的处理。
以下是一个通用的错误处理流程图:
graph TD;
A[执行ASPI命令] --> B{SRB_Status是否为SS_COMP};
B -- 是 --> C[命令成功完成];
B -- 否 --> D{检查SRB_HaStat和SRB_TargStat};
D --> E{是否为已知错误类型};
E -- 是 --> F[根据错误类型处理];
E -- 否 --> G[记录未知错误信息];
F --> H[重试命令或采取其他措施];
G --> H;
H --> I{是否重试成功};
I -- 是 --> C;
I -- 否 --> J[输出错误提示并终止操作];
10. 综合应用示例
假设我们需要编写一个程序,获取磁盘信息并设置超时时间,同时支持对SCSI总线的重新扫描。以下是一个综合示例代码:
#include <windows.h>
// 假设的常量定义
#define SC_GET_DISK_INFO 0x06
#define SC_RESCAN_SCSI_BUS 0x07
#define SC_GETSET_TIMEOUTS 0x08
#define SS_COMP 0x00
#define SRB_DIR_OUT 0x10
// SRB结构体定义(省略部分重复代码)
typedef struct
{
BYTE SRB_Cmd;
BYTE SRB_Status;
BYTE SRB_HaId;
BYTE SRB_Flags;
DWORD SRB_Hdr_Rsvd;
BYTE SRB_Target;
BYTE SRB_Lun;
// 其他字段...
} SRB_GetDiskInfo, *PSRB_GetDiskInfo;
typedef struct
{
BYTE SRB_Cmd;
BYTE SRB_Status;
BYTE SRB_HaId;
BYTE SRB_Flags;
DWORD SRB_Hdr_Rsvd;
} SRB_RescanPort, *PSRB_RescanPort;
typedef struct
{
BYTE SRB_Cmd;
BYTE SRB_Status;
BYTE SRB_HaId;
BYTE SRB_Flags;
DWORD SRB_Hdr_Rsvd;
BYTE SRB_Target;
BYTE SRB_Lun;
DWORD SRB_Timeout;
} SRB_GetSetTimeouts, *PSRB_GetSetTimeouts;
// 假设的SendASPI32Command函数
DWORD SendASPI32Command(LPSRB srb);
int main()
{
// 获取磁盘信息
SRB_GetDiskInfo srbGetDiskInfo;
memset(&srbGetDiskInfo, 0, sizeof(SRB_GetDiskInfo));
srbGetDiskInfo.SRB_Cmd = SC_GET_DISK_INFO;
srbGetDiskInfo.SRB_Target = 2;
srbGetDiskInfo.SRB_Lun = 0;
srbGetDiskInfo.SRB_HaId = 0;
DWORD dwASPIStatus = SendASPI32Command((LPSRB)&srbGetDiskInfo);
if (dwASPIStatus != SS_COMP)
{
// 处理获取磁盘信息失败的情况
// Error handling code goes here!
}
// 重新扫描SCSI总线
SRB_RescanPort srbRescanPort;
memset(&srbRescanPort, 0, sizeof(SRB_RescanPort));
srbRescanPort.SRB_Cmd = SC_RESCAN_SCSI_BUS;
srbRescanPort.SRB_HaId = 0;
dwASPIStatus = SendASPI32Command((LPSRB)&srbRescanPort);
if (dwASPIStatus != SS_COMP)
{
// 处理重新扫描失败的情况
// Error handling code goes here!
}
// 设置超时时间
SRB_GetSetTimeouts srbGetSetTimeouts;
memset(&srbGetSetTimeouts, 0, sizeof(SRB_GetSetTimeouts));
srbGetSetTimeouts.SRB_Cmd = SC_GETSET_TIMEOUTS;
srbGetSetTimeouts.SRB_Flags = SRB_DIR_OUT;
srbGetSetTimeouts.SRB_HaId = 0;
srbGetSetTimeouts.SRB_Target = 2;
srbGetSetTimeouts.SRB_Lun = 0;
srbGetSetTimeouts.SRB_Timeout = 200; // 设置超时时间为100秒(200个半秒)
dwASPIStatus = SendASPI32Command((LPSRB)&srbGetSetTimeouts);
if (dwASPIStatus != SS_COMP)
{
// 处理设置超时失败的情况
// Error handling code goes here!
}
return 0;
}
11. 总结
ASPI编程涉及到多种命令和超时设置,每个命令都有其特定的用途和参数设置。在实际开发中,需要根据具体的应用场景选择合适的命令,并注意不同操作系统下的差异。同时,合理设置超时时间可以避免程序长时间等待,提高程序的响应速度和稳定性。通过对错误处理流程的设计,可以增强程序的健壮性,确保在出现异常情况时能够正确处理。希望本文的介绍能帮助开发者更好地掌握ASPI编程,开发出高效、稳定的程序。
在未来的ASPI编程中,随着操作系统和硬件的不断发展,可能会有更多的功能和优化出现。开发者需要持续关注相关技术的更新,及时调整和改进自己的代码,以适应新的需求和挑战。
超级会员免费看
1266

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



