35、ATA/IDE与SCSI对比及ASPI示例应用解析

ATA/IDE与SCSI对比及ASPI示例应用解析

1. ATA/IDE与SCSI的应用场景对比

在不同的使用场景下,ATA/IDE和SCSI接口有着不同的适用性。以下是具体的分析:
|使用场景|推荐接口|原因|
| ---- | ---- | ---- |
|图形、视频处理或开发工作|SCSI|这些应用需要大量的磁盘活动,如后台编译、在内存中交换大图像文件等,且通常需要高度的多任务处理能力。ATA的顺序任务处理方式对时间要求更为严格,因此SCSI是更好的选择。|
|使用外部设备|SCSI|中质量的桌面扫描仪可使用USB接口,可能不足以成为选择SCSI的理由。但高端设备通常仅支持SCSI接口,而且使用这些设备时很可能会涉及到上述的图形工作,所以SCSI更合适。|
|使用特定操作系统(如Windows NT、Windows 2000或UNIX)|SCSI|这些操作系统需要大量的后台处理,会产生大量的磁盘活动,使用SCSI接口能在负载下提供更好的响应能力。|

总体而言,在大多数情况下,SCSI是更好、甚至更快的接口。但你需要根据自己的应用需求和预算来决定是否使用SCSI。

2. 小型ASPI示例应用介绍

ShowSCSI是一个用Delphi编写的小型程序,用于展示如何与ASPI接口进行通信。它主要由三个组件组成:
1. ShowSCSI.pas :主程序,提供图形用户界面(GUI)和程序逻辑。它调用AspiApplication.pas中的接口函数,并为调用结果提供容器列表。
2. AspiApplication.pas :中间层,提供高级功能调用,如“启动设备”或“弹出介质”等。它调用ASPI_Interface.pas中的函数,提交设备地址(主机适配器、SCSI ID、LUN)和一个用于保存返回值的对象。
3. ASPI_Interface.pas :最低层,是唯一与设备进行ASPI/SCSI通信的部分。在该单元中定义了ASPI/SCSI命令,例如ASPI_GetDeviceType用于获取指定SCSI设备的类型(磁盘、光驱等)。

以下是程序结构的mermaid流程图:

graph LR
    A[ShowSCSI.pas] --> B[AspiApplication.pas]
    B --> C[ASPI_Interface.pas]
3. 使用三层结构的原因

使用三层结构主要有以下三个目标:
1. 封装ASPI接口 :将ASPI接口封装在一层中,使得用户可以简单地说“我想打开CD-ROM托盘”,而无需了解在SCSI命令描述符块中设置启动/停止和加载/弹出位的具体细节。
2. 便于扩展 :虽然该程序只是ASPI/SCSI世界的小示例,但这种分层概念可用于更大的应用程序。在分层结构中,可以轻松扩展ASPI_Interface.pas中的SCSI函数列表,而无需更改项目的其他部分,避免代码变得混乱。
3. 分离用户界面和技术部分 :用户界面应与程序的技术部分严格分离。例如,可以将ShowSCSI.pas替换为控制台模式的部分,以便从命令行或批处理文件中弹出CD。

4. 程序的初始化和设备检测

程序启动时,ShowSCSI会搜索ASPI主机适配器(包括检查ASPI是否已安装),并将主机适配器数据收集到一个名为HAList的列表中。以下是初始化代码:

procedure TForm1.FormCreate(Sender: TObject);
...
HAList     :=TList.Create;
// list of host adapters
DeviceList :=TList.Create;
// list of actual devices
...
GetHAInfos(Memo.Lines);
// get HA's and display list in Memo
for i:=0 to HAList.count-1 do begin
    // load HA listbox with all HA's
    HAListBox.Items.Add(inttostr(PHADevices(HAList[i])^.HA)+': ' +
    PHADevices(HAList[i])^.HAName);
...
5. 获取主机适配器信息

GetHAInfos函数用于获取主机适配器信息,并将其显示在备忘录中。以下是该函数的代码:

function GetHAInfos(Protokoll:TStrings):boolean;
VAR NumAdap    : Longint;
    HA_Num     : integer;
    pString    : string ;
    AktHA      : PHADevices;
    PASPI_HAInfo:P_ASPI_HAInfo ;
begin
    GetHAInfos:=false;
    // initialise ASPI manager
    PASPI_HAInfo := New(P_ASPI_HAInfo) ;
    // from ASPI_Interface ...
    ASPI_GetHANum(NumAdap) ;
    // return the number of HAs [0..n]
    for HA_Num:=0 to NumAdap-1 do begin
        // ask every found hostadapter ...
        ASPI_GetHAInfos(HA_Num, PASPI_HAInfo) ;
        AktHA:=New(PHADevices);
        AktHA^.HA:=HA_Num;
        HAList.Add(AktHA) ;
        // add one entry (hostadapter) to the 
        //listbox "host adapters"
        pString := format('Hostadapter ' + PASPI_HAInfo^.HaName + 
        ' AspiNum: %d, SCSI-ID: %d', 
        [PASPI_HAInfo^.HaAspiId,PASPI_HAInfo^.HaScsiId]);
        Protokoll.add(pString);
        // add one entry to the 
        // memofield "device info"
        AktHA^.HAName := PASPI_HAInfo^.HaName;
    end;
end;
6. 选择主机适配器和设备检测

当选择一个主机适配器后,程序会使用SCSI Inquiry命令检查所有可能的设备ID,将查询数据写入一个名为DeviceList的新列表,并在GUI中显示设备。以下是选择主机适配器时的代码:

procedure TForm1.HAListBoxClick(Sender: TObject); // clicking on a HA
var
    i : integer;
    // adapter from the list ...
    HA:integer ;
    // Number of the selected hostadapter
begin
    HA := HAListBox.ItemIndex;
    // selected Host adapter, defined in
    // the ASPIApplication unit

    // form cleanup ....
    ListBox1.Clear;
    // clear devicelist
    ListBox1.Refresh;
    // refresh devicelist
    // action ...
    GetDeviceList(HA, DeviceList);
    // get devices and load memo field
    for i:=0 to DeviceList.count-1 do
        // fill device listbox
        ListBox1.Items.Add(inttostr(P_ASPI_DevInfo(DeviceList[i])^.ID)+':'+
        inttostr(P_ASPI_DevInfo(DeviceList[i])^.LUN)+': '+
        P_ASPI_DevInfo(DeviceList[i])^.Inquiry.ProductId);
end;
7. 选择设备并显示信息

当选择一个设备后,程序会在文本框(Memo)中显示该设备的查询数据。以下是选择设备时的代码:

procedure TForm1.ListBox1Click(Sender: TObject);
var
    DEVINDEX  : integer ;
begin
    Memo.Clear;
    // clear memofield
    DEVINDEX := ListBox1.ItemIndex;
    // selected Device (element from Listbox)
    with P_ASPI_DevInfo(DeviceList[DEVINDEX])^ do // add device information
        // to memolist "Devices"
    begin
        Memo.Lines.add(format('Device HA: %d : ID %d, LUN %d, Type %d = '
        +DevType,[HA,ID,LUN,TypeNum]));
        // output inquiry data in
        Memo.Lines.add(InquiryString);
        // memofield
    end;
end;
8. 发送SCSI命令

点击按钮可以向设备发送SCSI命令,例如点击“停止”按钮会发送Start/Stop Unit命令,将Start/Stop位设置为“停止”。以下是点击“停止”按钮时的代码:

procedure TForm1.StopButtonClick(Sender: TObject); //Stop Button
var i:integer;
begin
    for i:=0 to ListBox1.items.count-1 do
        if Listbox1.selected[i]
            // for each selected device
            then
                // send Start/Stop Unit command with
                // 'start' and 'eject' flags set false
                StartStopUnit(false,DeviceList[i]);
end;
9. 启动/停止设备的具体实现

StartStopUnit函数会填充SRB结构,并调用ASPI_StartStopUnit函数。以下是ASPI_StartStopUnit函数的代码:

function ASPI_StartStopUnit(HA,ID,LUN:integer; Start:boolean; 
var errorcode:integer):boolean ;
var Buffer:PSRBBuf;
    // PSRBBuf from Wnaspi32
    SRB:PSRB_ExecSCSICmd;
    // PSRB_ExecSCSICmd from Wnaspi32
begin
    SRB:=New(PSRB_ExecSCSICmd);
    // create new SRB structures
    Buffer:=New(PSRBBuf);
    InitSRB(SRB,sizeOf(SRB^));
    // initialize SRB
    SRB^.SRB_HAId:=HA;
    // fill SRB parameters ...
    SRB^.SRB_Target:=ID;
    SRB^.SRB_Lun:=LUN;
    SRB^.SRB_BufPointer:=Buffer;
    SRB^.SRB_CMD:=SC_EXEC_SCSI_CMD;
    SRB^.SRB_Flags:=0;
    SRB^.SRB_BufPointer:=nil;
    // no buffer:
    SRB^.SRB_Buflen:=0;
    // buffer size 0, the Start/Stop Unit
    //   command doesn't transfer data...
    SRB^.SRB_SenseLen:=SENSE_LEN;
    // default ASPI sense buffer length, 14 bytes
    SRB^.SRB_CDBLen:=6;
    // 6-Byte command
    // ---- SCSI command block parameters ----
    SRB^.CDBByte[0]:=$1B;
    // Start/Stop Unit $1B
    SRB^.CDBByte[1]:=LUN*32;
    // LUN shifted 5 bits to the left
    // where it belongs ....
    if Start then
        // set SCSI command - start bit
        SRB^.CDBByte[4]:=1
    else
        SRB^.CDBByte[4]:=0;
    SendASPI32Command(SRB);
    // action!
    Now the program polls the ASPI status until it indicates that the command has
    completed. Because it’s pointless to poll a few million times until a start/stop
    unit command completes, we add 100ms pauses to release the CPU for this time. 
    while SRB^.SRB_Status=0 do begin
        sleep(100) ;
        // don't lockup the machine...
    end ;

    If the command has completed, the SCSI target status of the device is checked.
    This would be the place to implement a SCSI error handler, if you want or need
    one. At the moment, we check only if the command succeeded or not.
    case SRB^.SRB_TargStat of       // This handler may be used later to repeat
        // a command based on certain conditions
        TARGSTAT_GOOD:             // All done now
        begin
            ASPI_StartStopUnit:=True ;
        end;
        TARGSTAT_CHKCOND:          // Check Condition,
        begin                      // e.g. process sense data
            ASPI_StartStopUnit:=False ;
        end;
        TARGSTAT_BUSY:             // Device is Busy
        begin
            ASPI_StartStopUnit:=False ;
        end;
        TARGSTAT_RESCONF:          // Reservation Conflict
        begin
            ASPI_StartStopUnit:=False ;
        end;
        else
        begin                       // there may be very special cases...
            ASPI_StartStopUnit:=False ;
        end;
    end ;
    Finally, the data structures used for the SCSI function call are freed.
    // cleanup data structures...
    dispose(SRB);
    dispose(Buffer);
end ;
10. 程序存在的问题

该程序存在两个已知问题:
1. ASPI轮询机制问题 :程序在每次ASPI轮询SRB状态后添加了100ms的暂停,以避免占用100%的CPU时间。在编写更复杂或商业应用时,应使用“ASPI发布”和ASPI SC_GETSET_TIMEOUTS命令来设置超时。
2. 设备响应问题 :在Windows 95、98或NT4的标准ASPI层中,所有SCSI主机适配器接口或模拟接口都会被列为主机适配器。如果某个设备不响应查询命令,ASPI层会锁定等待响应,可能导致系统崩溃。一种简单的解决方法是过滤主机适配器的名称,但这不是一个好的解决方案。同样,可以使用ASPI SC_GETSET_TIMEOUTS命令来设置超时。

11. 加载/弹出介质功能的实现

要添加加载/弹出介质的功能,可以使用与其他命令相同的函数布局。以下是ASPI_LoadEjectUnit函数的代码:

function ASPI_LoadEjectUnit(HA,ID,LUN:integer; Eject:boolean; 
var errorcode:integer) : boolean ;
{ Description: Start/Stop Unit with Eject bit set
Parameters:  HA, ID, LUN of the SCSI device
Eject bit set true/false
Errorcode (return value for e.c. - not yet implemented)
Returns True/False
}
var Buffer:PSRBBuf;
    // PSRBBuf from Wnaspi32
    SRB:PSRB_ExecSCSICmd;
    // PSRB_ExecSCSICmd from Wnaspi32
begin
    SRB:=New(PSRB_ExecSCSICmd);
    Buffer:=New(PSRBBuf);
    InitSRB(SRB,sizeOf(SRB^));
    SRB^.SRB_HAId:=HA;
    SRB^.SRB_Target:=ID;
    SRB^.SRB_Lun:=LUN;
    SRB^.SRB_BufPointer:=Buffer;
    SRB^.SRB_CMD:=SC_EXEC_SCSI_CMD;
    SRB^.SRB_Flags:=0;
    SRB^.SRB_BufPointer:=nil;
    // no buffer:
    SRB^.SRB_Buflen:=0;
    // buffer size 0, for the Start/Stop Unit
    // command doesn't transfer data...
    SRB^.SRB_SenseLen:=SENSE_LEN;
    // default ASPI sense buffer length, 14 bytes
    SRB^.SRB_CDBLen:=6;
    / 6-Byte command
    // SCSI command block parameters
    SRB^.CDBByte[0]:=$1B;
    // Start/Stop Unit $1B
    SRB^.CDBByte[1]:=LUN*32;
    // LUN shifted 5 bits to the left
    // where it belongs ....
    if Eject then
        // set SCSI command - start and eject bit
        SRB^.CDBByte[4] := 2
    else
        SRB^.CDBByte[4] := 3 ;
    SendASPI32Command(SRB);
    // action!
    while SRB^.SRB_Status=0 do begin
        sleep(100) ;
        // ASPI command pending  ...
    end;
    case SRB^.SRB_TargStat of
        // This handler may be used later
        // to repeat a command based on
        // special conditions
        TARGSTAT_GOOD:
            // All done now
            begin
                ASPI_LoadEjectUnit:=True ;
            end;
        TARGSTAT_CHKCOND:
            // Check Condition,
            begin
                // e.g. process sense data
                ASPI_LoadEjectUnit:=False ;
            end;
        TARGSTAT_BUSY:
            // Device is Busy
            begin
                ASPI_LoadEjectUnit:=False ;
            end;
        TARGSTAT_RESCONF:
            // Reservation Conflict
            begin
                ASPI_LoadEjectUnit:=False ;
            end;
        else
            begin
                // there may be some very special cases
                ASPI_LoadEjectUnit:=False ;
            end;
    end ;
    // cleanup data structures...
    dispose(SRB);
    dispose(Buffer);
end ;

通过以上的分析,我们对ATA/IDE与SCSI的应用场景有了更清晰的认识,同时也了解了如何使用ASPI接口与SCSI设备进行通信,以及如何实现一些基本的功能。在实际应用中,需要根据具体需求选择合适的接口和功能实现方式,并注意解决可能出现的问题。

ATA/IDE与SCSI对比及ASPI示例应用解析

12. 加载/弹出功能实现流程详解

为了更清晰地理解加载/弹出介质功能的实现,下面详细介绍其流程:
1. 初始化SRB结构 :创建新的SRB和缓冲区对象,并初始化SRB结构。

SRB:=New(PSRB_ExecSCSICmd);
Buffer:=New(PSRBBuf);
InitSRB(SRB,sizeOf(SRB^));
  1. 设置SRB参数 :填充SRB的各个参数,包括主机适配器ID、目标设备ID、逻辑单元号(LUN)等。
SRB^.SRB_HAId:=HA;
SRB^.SRB_Target:=ID;
SRB^.SRB_Lun:=LUN;
SRB^.SRB_BufPointer:=Buffer;
SRB^.SRB_CMD:=SC_EXEC_SCSI_CMD;
SRB^.SRB_Flags:=0;
SRB^.SRB_BufPointer:=nil;
SRB^.SRB_Buflen:=0;
SRB^.SRB_SenseLen:=SENSE_LEN;
SRB^.SRB_CDBLen:=6;
  1. 设置SCSI命令块参数 :根据是否弹出介质的标志,设置SCSI命令块的相应字节。
SRB^.CDBByte[0]:=$1B;
SRB^.CDBByte[1]:=LUN*32;
if Eject then
    SRB^.CDBByte[4] := 2
else
    SRB^.CDBByte[4] := 3 ;
  1. 发送命令并等待完成 :调用 SendASPI32Command 函数发送命令,并通过轮询SRB状态等待命令完成,期间添加100ms的暂停以释放CPU。
SendASPI32Command(SRB);
while SRB^.SRB_Status=0 do begin
    sleep(100) ;
end;
  1. 检查目标状态 :根据SRB的目标状态判断命令是否成功。
case SRB^.SRB_TargStat of
    TARGSTAT_GOOD:
        begin
            ASPI_LoadEjectUnit:=True ;
        end;
    TARGSTAT_CHKCOND:
        begin
            ASPI_LoadEjectUnit:=False ;
        end;
    TARGSTAT_BUSY:
        begin
            ASPI_LoadEjectUnit:=False ;
        end;
    TARGSTAT_RESCONF:
        begin
            ASPI_LoadEjectUnit:=False ;
        end;
    else
        begin
            ASPI_LoadEjectUnit:=False ;
        end;
end ;
  1. 清理数据结构 :释放SRB和缓冲区对象。
dispose(SRB);
dispose(Buffer);

以下是该流程的mermaid流程图:

graph TD;
    A[初始化SRB结构] --> B[设置SRB参数];
    B --> C[设置SCSI命令块参数];
    C --> D[发送命令并等待完成];
    D --> E[检查目标状态];
    E --> F[清理数据结构];
13. 总结ATA/IDE与SCSI的特点
接口类型 优点 缺点 适用场景
ATA/IDE 成本较低,使用广泛 顺序任务处理对时间要求严格,多任务处理能力相对较弱 对磁盘活动要求不高的普通应用场景
SCSI 多任务处理能力强,适用于高负载磁盘活动场景,高端设备支持多 成本较高 图形、视频处理、开发工作,使用高端外部设备,特定操作系统(如Windows NT、Windows 2000、UNIX)
14. 优化ASPI应用程序的建议

针对前面提到的程序存在的问题,以下是一些优化建议:
1. 解决ASPI轮询机制问题
- 使用“ASPI发布”代替轮询机制,避免占用过多CPU资源。
- 使用ASPI SC_GETSET_TIMEOUTS命令设置超时时间,防止程序长时间等待。
2. 解决设备响应问题
- 过滤主机适配器名称,排除已知可能导致问题的适配器。
- 同样使用ASPI SC_GETSET_TIMEOUTS命令设置超时,避免ASPI层长时间锁定等待响应。

15. 未来功能扩展方向

基于现有的ASPI示例应用程序,可以考虑以下功能扩展方向:
1. 增加更多SCSI命令支持 :除了启动/停止和加载/弹出功能,还可以实现其他SCSI命令,如读取设备信息、写入数据等。
2. 优化用户界面 :提供更友好的用户界面,方便用户操作和查看设备信息。
3. 错误处理和日志记录 :完善错误处理机制,记录程序运行过程中的错误信息,方便调试和维护。

16. 代码复用和模块化设计

该示例应用程序采用了三层结构,这种设计有利于代码复用和模块化开发。以下是各层的主要职责和复用方式:
1. ShowSCSI.pas(主程序层) :负责提供GUI和程序逻辑,调用中间层的函数。可以将GUI部分进行封装,方便在不同的项目中复用。
2. AspiApplication.pas(中间层) :提供高级功能调用,调用底层的ASPI接口函数。可以将常用的功能封装成独立的函数库,供其他项目使用。
3. ASPI_Interface.pas(底层) :与设备进行ASPI/SCSI通信,定义SCSI命令。可以将该层的代码作为一个基础库,在不同的ASPI应用中复用。

17. 实际应用案例分析

假设我们要开发一个图形处理应用程序,需要频繁地读写大图像文件。在这种情况下,选择SCSI接口和使用ASPI进行设备通信是一个不错的选择。以下是具体的实现步骤:
1. 选择硬件 :选择支持SCSI接口的磁盘阵列或外部存储设备。
2. 安装驱动 :安装相应的SCSI驱动程序,确保系统能够识别设备。
3. 开发应用程序 :使用上述的ASPI示例应用程序作为基础,进行功能扩展。
- 实现大图像文件的读写功能,利用SCSI接口的高速数据传输能力。
- 优化多任务处理,确保在处理多个图像文件时不会出现性能瓶颈。
4. 测试和优化 :对应用程序进行测试,根据测试结果进行性能优化,确保程序的稳定性和可靠性。

18. 结论

通过对ATA/IDE与SCSI接口的对比分析,以及对ASPI示例应用程序的详细介绍,我们了解到在不同的应用场景下应选择合适的接口。SCSI接口在高负载磁盘活动场景下具有明显的优势,而ASPI提供了一种方便的方式来与SCSI设备进行通信。在开发ASPI应用程序时,要注意解决可能出现的问题,并采用合理的设计和优化方法,以提高程序的性能和可维护性。未来,可以根据实际需求对应用程序进行功能扩展,以满足不断变化的业务需求。

总之,深入理解ATA/IDE与SCSI接口的特点,掌握ASPI的使用方法,对于开发高性能的磁盘应用程序具有重要意义。希望本文能够为相关开发者提供有价值的参考。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值