从 SCSI 磁带读取时“无法分配内存”

Mal*_*ous 11 tar dd scsi tape

我正在试验一些旧的 SCSI 磁带驱动器,我已经成功地将一些数据写入磁带,但我正在努力尝试再次读取它。

# tar tvf /dev/st0
tar: /dev/st0: Cannot read: Cannot allocate memory
tar: At beginning of tape, quitting now
tar: Error is not recoverable: exiting now

# dd if=/dev/st0 of=test
dd: error reading '/dev/st0': Cannot allocate memory
0+0 records in
0+0 records out
0 bytes copied, 3.20155 s, 0.0 kB/s
Run Code Online (Sandbox Code Playgroud)

在这些命令之后,dmesg说:

st 10:0:3:0: [st0] Block limits 1 - 16777215 bytes.
st 10:0:3:0: [st0] Failed to read 65536 byte block with 512 byte transfer.
st 10:0:3:0: [st0] Failed to read 131072 byte block with 65536 byte transfer.
st 10:0:3:0: [st0] Failed to read 65536 byte block with 10240 byte transfer.
st 10:0:3:0: [st0] Failed to read 94208 byte block with 69632 byte transfer.
st 10:0:3:0: [st0] Failed to read 65536 byte block with 10240 byte transfer.
st 10:0:3:0: [st0] Failed to read 65536 byte block with 512 byte transfer.
Run Code Online (Sandbox Code Playgroud)

其中大部分是因为我正在使用该tar -b选项测试不同的块大小,但这些都没有任何影响。

有时我能够从磁带上的第一个块中读取几 kB 的数据(tar 可以提取直到数据中断),但通常它会失败,根本没有读取任何数据。

我(显然)成功地将数据写入磁带,将磁带移动到另一个驱动器,寻找到数据的末尾然后写入更多,因此将数据写入驱动器似乎没有困难,只需将其读回再次。

我正在使用两个 LTO-3 驱动器。一个是半高的HP Ultrium 920,另一个是全高的HP Ultrium 960。两者都有这个问题。我曾尝试使用两种不同的 SCSI 卡(LSI Logic Ultra320 卡和 Adaptec Ultra2/SE 40MB/sec 卡),两者都产生相同的错误。

我尝试了一条带有终端器的电缆(即使在 Ultra320 卡上也给了我 40MB/秒的速度),然后是一个双连接器电缆,这意味着我只能连接一个驱动器,所以我启用了驱动器上的“术语电源”跳线,让我使用 Ultra160(即使驱动器和控制器都是 Ultra320)但是这一切都没有改变,并且在整个过程中,我在尝试从驱动器读取时仍然遇到相同的错误。

我从 Linux 内核 4.10.13 降级到 4.4.3(本机上以前的版本),错误信息从“无法分配内存”变为“输入/输出错误”,但问题仍然存在。

任何可能导致此错误的想法?

编辑: 40MB/秒的问题是因为我使用了 SE 主动终结器。一旦我用 LVD 终结器替换它,速度就上升到 Ultra160。我想我需要新的电缆来连接 Ultra320,但这现在是磁带带宽的两倍(最大 80MB/秒),所以暂时对我来说没问题。虽然与错误消息没有区别。

Mal*_*ous 14

好的,我想我已经解决了这个问题。

TL; 博士

使用dd大块大小从磁带读取:

dd if=/dev/nst0 bs=1M | tar tvf -
Run Code Online (Sandbox Code Playgroud)

背景

写入磁带时,数据以称为块的单位写入。这些就像硬盘上的扇区。多年来,硬盘块固定为 512 字节,最近才移动到 4096 字节块,磁带块可以设置为您喜欢的任何大小。

您希望使用的块大小通过以下setblk子命令设置mt-st

mt-st -f /dev/nst0 setblk 512    # Use 512-byte blocks
mt-st -f /dev/nst0 setblk 64k    # Use 65536-byte blocks
Run Code Online (Sandbox Code Playgroud)

当您向驱动器发出读取操作时,它将以块大小的块形式返回数据。您不能读取半个块 - 您可以从磁带读取的最小数据量是一个块,当然这可能是任意数量的实际字节,具体取决于块大小。

这意味着如果您使用的程序提供 16kB 内存缓冲区,您将能够一次从磁带中读取多达 32 个块,其中 512 字节块正好适合 16kB 缓冲区。但是,您将无法从具有 64kB 块的磁带中读取任何内容,因为您甚至无法将其中一个块放入 16kB 缓冲区,并且请记住,您一次读取的内容不能少于一整块。

如果您尝试这样做,通过使用对于一个块来说太小的缓冲区,驱动程序(在这种情况下是stSCSI 磁带驱动程序)将返回一个内存分配错误代码,以告知您您的读取缓冲区太小而无法容纳一个单块。

更复杂的是,一些磁带驱动器(显然是我正在使用的 LTO 驱动器)也支持可变大小的块。这意味着块大小由每次写入操作的大小决定,并且每个块的大小可以与最后一个不同。

此模式设置为块大小为零:

mt-st -f /dev/nst0 setblk 0    # Use variable-sized blocks
Run Code Online (Sandbox Code Playgroud)

这也是默认选项 - 大概,我在这里猜测 - 它会因配置不正确的程序浪费更少的空间。例如,如果您设置了 4k 块,但您的程序一次仅以 512 字节为单位写入数据,则每个 512 字节的数据块可能会占用磁带上的 4k。

原因

如果你现在把所有东西放在一起,你会发现磁带可以有一个 512 字节的块,然后是一个 64kB 的块。如果程序正在读取带有 16kB 缓冲区的磁带,它将成功读取第一个块,但是当它尝试读取更多时,它将无法在其缓冲区中容纳下一个 64kB 块,因此驱动程序将返回一个错误。

这解释了为什么我Cannot allocate memory大部分时间都会出错,有时我能够使用 tar 来提取前几个文件,但随后又出现了错误。我没有设置块大小,mt-st所以在写入磁带时它默认为可变大小的块,现在tar使用的缓冲区太小而无法读取其中一些块。

tar几个选项可用于设置自己的内部块大小,即--blocking-factor--read-full-records--record-size,但是这些仅在tar用于直接读取和写入磁带时才有效。

由于我通过mbuffer程序写入磁带以减少磁带擦鞋,因此tar存档中的块大小不再与磁带上的块大小匹配。这意味着--blocking-factor几乎没有影响 - 它会允许读取磁带上的第一个块,其中包括一个标头,告诉tar阻塞因子应该是什么,其中它切换到那个并忽略命令行上给出的值。这意味着无法再读取第二个和后续块!

解决方案

解决方案是使用另一个程序从磁带中读取 - 可以将读取缓冲区大小设置为足够大的值以容纳我们可能会看到的最大块。

dd 为此工作,在紧要关头,这有效:

dd if=/dev/nst0 bs=256k | tar tvf -
Run Code Online (Sandbox Code Playgroud)

256k如果您的磁带上有更大的块,您可能需要增加,但这对我有用。 1M也可以正常工作,因此如果值太大,在合理范围内似乎并不重要。