磁带数据存储。第二部分:介质更换器 - 状态命令






4.50/5 (3投票s)
C# 中介质更换器状态命令的实现。
引言
本文档描述了开发软件应用程序以与介质更换器通信以及获取其内部结构基本信息所涉及的步骤和问题。
发现更换器名称
应该执行的第一个操作是为介质更换器创建操作系统句柄。可以通过 CreateFile
API 获取句柄。
[DllImport("kernel32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern SafeFileHandle CreateFile(
uint dwDesiredAccess,
uint dwShareMode,
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
如您所见,第一个参数是文件名。大多数附加的硬件设备都会获得唯一的系统名称,也称为 DOS 名称。介质更换器可以获得以下系统名称:MediaChangeX
、ChangerX
。要获取确切的名称,我们可以调用 Win API 方法
[DllImport("kernel32.dll")]
static extern uint QueryDosDevice(string lpDeviceName,
IntPtr lpTargetPath, uint ucchMax);
private static string QueryDosDevice()
{
IntPtr mem = Marshal.AllocHGlobal(1024);
try
{
if (mem == IntPtr.Zero)
throw new Win32Exception("Failed to allocate unmanaged memory");
uint returnSize = (int)QueryDosDevice(null, mem, (uint)maxSize);
if (returnSize == 0)
throw new Win32Exception("Failed to obtain DOS devices info");
string allDevices = Marshal.PtrToStringAnsi(mem, returnSize);
return allDevices;
}
finally
{
if(mem != IntPtr.Zero )
Marshal.FreeHGlobal(mem);
}
}
// Output(Fragment)
// ...
//Scsi3:
//DISPLAY3
//ACPI#GenuineIntel_-_EM64T_Family_6_Model_44#12#{97fadb10-4e33-40ae-359c-8bef029dbdd0}
//ACPI#GenuineIntel_-_EM64T_Family_6_Model_44#_8#{97fadb10-4e33-40ae-359c-8bef029dbdd0}
//ACPI#IPI0001#5#{4116f60b-25b3-4662-b732-99a6111edc0b}
//Root#*ISATAP#0002#{cac88484-7515-4c03-82e6-71a87abac361}
//FltMgrMsg
//HID#VID_0624&PID_0248&MI_00#7&2c1758c0&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
//ACPI#GenuineIntel_-_EM64T_Family_6_Model_44#_2#{97fadb10-4e33-40ae-359c-8bef029dbdd0}
//B06BDRV#L2ND&PCI_163914E4&SUBSYS_02351028&REV_20#5&3785ac6a&0&20050200#{cac88484-
//7515-4c03-82e6-71a87abac361}
//STORAGE#Volume#1&19f7e59c&0&GptPartition{a132face-928a-46a7-9bd2-e0351c99588e}#
//{7f108a28-9833-4b3b-b780-2c6b5fa5c062}
//Changer0
//Nm3_{C41A3799-2F0C-471E-88E3-EE5BBA8E487E}-
//{6E022F38-AB31-44C5-8206-2EB023EFF145}-0000
//USB#ROOT_HUB20#4&37084163&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8}
// ...
返回的 string
将包含所有系统 DOS 名称。搜索 Changer
或 MediaChanger
字样。您将找到您的介质更换器的确切名称。如果您的机器连接了两个或多个介质更换器,可以使用 DeviceIOControl
获取地址信息,例如特定 SCSI 目标的 Target ID (TID) 和逻辑单元号 (LUN)。这样您就可以获得分配给您正在使用的设备的 DOS 名称信息。
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool DeviceIoControl(
SafeFileHandle hDevice,
uint dwIoControlCode,
IntPtr lpInBuffer,
uint nInBufferSize,
IntPtr lpOutBuffer,
uint nOutBufferSize,
out uint lpBytesReturned,
IntPtr lpOverlapped);
private static string MapDosName(string fileHandle)
{
IntPtr ptr = Marshal.AllocHGlobal(1024);
try
{
bool status = SCSI.DeviceIoControl(
fileHandle,
SCSI.IOCTL_SCSI_GET_ADDRESS,
IntPtr.Zero,
0,
ptr,
1024,
out returned,
IntPtr.Zero);
if (status == false)
{
return string.Format("Error opening the DEVICE {0}",
Marshal.GetLastWin32Error());
}
return string.Format("Length={0}, PortNumber={1}, PathId={2},
TargetId={3}, Lun={4}",
Marshal.ReadInt32(ptr, 0),
Marshal.ReadByte(ptr, 4),
Marshal.ReadByte(ptr, 5),
Marshal.ReadByte(ptr, 6),
Marshal.ReadByte(ptr, 7));
}
finally
{
Marshal.Release(ptr);
}
}
// Output (fileHandle is created from \\.\Changer0):
//
// Changer0
// Length:8, TargetID:2, LUN:1, PathID:0, Port:3
//
发现更换器结构
现在,我们已经有足够的信息来处理介质更换器设备了,因此我们将重点放在导致 SCSI 总线更换器设备返回自身信息的 API 函数上。例如,某些设备可以具有以下内部结构(逻辑):

如您所记得,磁带数据存储是两种独立设备的集成,即磁带驱动器和介质更换器。介质更换器由存储和移动磁带盒所需的全部机械和电子设备组成,而磁带驱动器则提供读/写功能。因此,当磁带插入设备时,它会进入邮件槽。从邮件槽,它可以移动到常规插槽(插槽 1-8)或驱动器。当磁带位于驱动器中时,可以执行 IO 操作。让我们看看如何通过 C# 代码进行发现。一种可能的方法是执行 IOCTL_CHANGER_GET_ELEMENT_STATUS
调用。请参阅 Discover
方法,该方法将为每个现有的常规插槽和驱动器输出状态。
public Changer.ChangerElementStatusEx
GetElementStatus(uint address, Changer.ElementType elementType)
{
IntPtr ptrIn = IntPtr.Zero;
IntPtr ptrOut = IntPtr.Zero;
try
{
m_Log.Debug("ENTER GetElementStatus");
Changer.ChangerReadElementStatus readStatusIn =
new Changer.ChangerReadElementStatus();
readStatusIn.ElementList.NumberOfElements = 1;
readStatusIn.ElementList.Element.ElementAddress = address;
readStatusIn.ElementList.Element.ElementType = elementType;
readStatusIn.VolumeTagInfo = 1;
ptrIn = Marshal.AllocHGlobal(Marshal.SizeOf(readStatusIn));
Marshal.StructureToPtr(readStatusIn, ptrIn, true);
Changer.ChangerElementStatusEx driveStatus =
new Changer.ChangerElementStatusEx();
ptrOut = Marshal.AllocHGlobal(Marshal.SizeOf(driveStatus));
uint readBytes;
if (false == Changer.DeviceIoControl(
ChangerHandle, // handle to device
(uint)Changer.IOCTL_CHANGER_GET_ELEMENT_STATUS, // dwIoControlCode
ptrIn, // lpInBuffer
(uint)Marshal.SizeOf(readStatusIn), // nInBufferSize
ptrOut, // output buffer
(uint)Marshal.SizeOf(driveStatus), // size of output buffer
out readBytes, // number of bytes returned
IntPtr.Zero // OVERLAPPED structure
))
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error, "Cannot access device, error:" + error);
}
driveStatus = (Changer.ChangerElementStatusEx)Marshal.PtrToStructure
(ptrOut, driveStatus.GetType());
return driveStatus;
}
catch (Exception e)
{
m_Log.Error("GetElementStatus", e);
throw;
}
finally
{
if (ptrIn != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptrIn);
}
if (ptrOut != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptrOut);
}
m_Log.Debug("EXIT GetElementStatus");
}
}
public void Discover()
{
int errorCode = 0;
Changer.ChangerElementStatusEx status;
for(int i = 0; i++; i < int.MaxValue)
{
try
{
status = GetElementStatus((uint)i, Changer.ElementType.ChangerSlot);
m_Log.InfoFormat("Regular slot {0} is {1} empty",
i, status.IsEmpty ? "" : "not");
}
catch (Win32Exception e)
{
if (e.NativeErrorCode ==
ERROR_ILLEGAL_ELEMENT_ADDRESS) break; //no more elements
throw;
}
}
status = m_TapeOperator.GetElementStatus(0, Changer.ElementType.ChangerDrive);
m_Log.InfoFormat("Driver is {0} empty", status.IsEmpty ? "" : "not");
}
结论
本文涵盖了介质更换器的状态功能。在获得介质更换器的状态后,我们可以执行 MOVE
、EJECT
、INSERT
和 IO
功能,以执行相当不错的备份系统。
有关 IO
功能,请参阅磁带数据存储。第一部分。
MOVE
、EJECT
和 INSERT
将在下一篇文章中介绍。
历史
- 2010 年 10 月 28 日:初始发布