24、ASPI编程详解:从Windows到Win32

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().
}
  1. 使用 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" );
  1. 检查函数地址是否有效:
    • 如果使用的是旧版本的ASPI,最后三个函数地址将为 NULL ,此时应禁用ASPI模块中所有新功能的使用。
    • 同时,检查 pfnGetASPI32SupportInfo pfnSendASPI32Command 是否为 NULL ,如果为 NULL ,表示访问DLL时出错,应用程序应停止使用ASPI,并使用 FreeLibrary 卸载 WNASPI32.DLL
  2. 使用获取的函数地址调用函数:
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请求处理。

需求响应动态冰蓄冷系统与需求响应策略的优化研究(Matlab代码实现)内容概要:本文围绕“需求响应动态冰蓄冷系统与需求响应策略的优化研究”展开,基于Matlab代码实现,重点探讨了冰蓄冷系统在电力需求响应背景下的动态建模与优化调度策略。研究结合实际电力负荷与电价信号,构建系统能耗模型,利用优化算法对冰蓄冷系统的运行策略进行求解,旨在降低用电成本、平衡电网负荷,并提升能源利用效率。文中还提及该研究为博士论文复现,涉及系统建模、优化算法应用与仿真验证等关键技术环节,配套提供了完整的Matlab代码资源。; 适合人群:具备一定电力系统、能源管理或优化算法基础,从事科研或工程应用的研究生、高校教师及企业研发人员,尤其适合开展需求响应、综合能源系统优化等相关课题研究的人员。; 使用场景及目标:①复现博士论文中的冰蓄冷系统需求响应优化模型;②学习Matlab在能源系统建模与优化中的具体实现方法;③掌握需求响应策略的设计思路与仿真验证流程,服务于科研项目、论文写作或实际工程方案设计。; 阅读建议:建议结合提供的Matlab代码逐模块分析,重点关注系统建模逻辑与优化算法的实现细节,按文档目录顺序系统学习,并尝试调整参数进行仿真对比,以深入理解不同需求响应策略的效果差异。
综合能源系统零碳优化调度研究(Matlab代码实现)内容概要:本文围绕“综合能源系统零碳优化调度研究”,提供了基于Matlab代码实现的完整解决方案,重点探讨了在高比例可再生能源接入背景下,如何通过优化调度实现零碳排放目标。文中涉及多种先进优化算法(如改进遗传算法、粒子群优化、ADMM等)在综合能源系统中的应用,涵盖风光场景生成、储能配置、需求响应、微电网协同调度等多个关键技术环节,并结合具体案例(如压缩空气储能、光热电站、P2G技术等)进行建模与仿真分析,展示了从问题建模、算法设计到结果验证的全流程实现过程。; 适合人群:具备一定电力系统、能源系统或优化理论基础,熟悉Matlab/Simulink编程,从事新能源、智能电网、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①开展综合能源系统低碳/零碳调度的科研建模与算法开发;②复现高水平期刊(如SCI/EI)论文中的优化模型与仿真结果;③学习如何将智能优化算法(如遗传算法、灰狼优化、ADMM等)应用于实际能源系统调度问题;④掌握Matlab在能源系统仿真与优化中的典型应用方法。; 阅读建议:建议结合文中提供的Matlab代码与网盘资源,边学习理论模型边动手调试程序,重点关注不同优化算法在调度模型中的实现细节与参数设置,同时可扩展应用于自身研究课题中,提升科研效率与模型精度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值