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^));
- 设置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;
- 设置SCSI命令块参数 :根据是否弹出介质的标志,设置SCSI命令块的相应字节。
SRB^.CDBByte[0]:=$1B;
SRB^.CDBByte[1]:=LUN*32;
if Eject then
SRB^.CDBByte[4] := 2
else
SRB^.CDBByte[4] := 3 ;
-
发送命令并等待完成
:调用
SendASPI32Command函数发送命令,并通过轮询SRB状态等待命令完成,期间添加100ms的暂停以释放CPU。
SendASPI32Command(SRB);
while SRB^.SRB_Status=0 do begin
sleep(100) ;
end;
- 检查目标状态 :根据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 ;
- 清理数据结构 :释放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的使用方法,对于开发高性能的磁盘应用程序具有重要意义。希望本文能够为相关开发者提供有价值的参考。
超级会员免费看
44

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



