Linux RAID-1、4、5 代码

作者:Gadi Oxman

使用 RAID(独立磁盘冗余阵列)是提高系统 I/O 性能和可靠性的常用方法。磁盘阵列有不同的级别,涵盖了提高系统 I/O 性能和增强可靠性的各种可能性。

本报告描述了内核中 RAID 驱动程序的当前实现,以及我们为支持提供更高可靠性的新型磁盘阵列配置而对内核所做的更改。

多设备 (MD) 驱动程序

MD 驱动程序用于将一组块设备组合成一个更大的块设备。通常,一组 SCSI 和 IDE 设备被配置成一个 MD 设备。正如在 Linux 2.0 内核中发现的那样,它被设计为在两种不同的模式(特性)下将扇区/设备元组重新映射到新的扇区/设备元组:线性(追加模式)和条带化(RAID-0 模式)。

线性模式只是一种将两个较小的块设备的内容连接成一个较大的设备的方法。这可以用于将几个小磁盘连接起来以创建一个更大的磁盘。新磁盘的大小是较小磁盘的总和。例如,假设我们有两个磁盘,每个磁盘有 300 个扇区;在我们将它们配置为线性 MD 设备后,我们得到一个新的 MD 设备,它有 600 个扇区:该设备的扇区 0 到 299 映射到第一个磁盘,扇区 300 到 599 映射到第二个磁盘。

RAID-0 模式(也称为条带化)更有趣。这种操作模式在将信息写入设备的同时,将信息分布在作为磁盘阵列一部分的磁盘上。与线性模式不同,这不仅仅是磁盘阵列组件的连接;条带化平衡了磁盘之间的 I/O 负载,从而实现了高吞吐量。这是大多数想要速度的人选择的特性。

The Linux RAID-1, 4, 5 Code

图 1 显示了四个磁盘在这种模式下的排列方式。阴影区域是那些提供冗余信息的区域,而那些堆叠起来的磁盘代表一个磁盘。正如你所看到的,图中没有阴影区域。这意味着什么?嗯,这意味着如果磁盘阵列的任何元素出现硬件问题,您将丢失所有信息。

线性特性和条带化特性都缺乏冗余和错误恢复模式。如果磁盘阵列的任何元素发生故障,则整个 MD 设备的内容都将变得无用,并且几乎没有希望恢复任何有用的信息。这类似于常规辅助存储设备发生的情况——如果它发生故障,您将丢失您的信息。但是,使用 RAID-0,您丢失信息的风险高于使用普通磁盘。较高的故障率是由于您拥有更多的磁盘,并且任何磁盘发生故障都会使 RAID-0 内容无法使用。

如果您有良好的备份策略,并且如果您不介意在任何磁盘发生故障时丢失一天的工作,那么使用 RAID-0 可能是最好的选择。例如,RAID-0 用于 comp.unix 等新闻组,但更高可靠性的 RAID 级别用于 alt.binaries.pictures.furniture 等重要新闻组。

内核中 MD 驱动程序支持这两种特性的方式非常简单;低级 ll_rw_blk 例程负责将块驱动程序 I/O 请求放在系统请求队列中。必须修改此例程,以调用作为 MD 驱动程序一部分的映射函数,并且每当对 MD 设备上的块发出请求时都会调用该函数。

ll_rw_blk 例程在概念上看起来像这样

ll_rw_blk (blocks)
{
    sanity-checks ();
    for-each block in blocks {
        make_request (block);
    }
}

它被修改为以这种方式支持条带化 (RAID-0) 和线性特性

ll_rw_blk (blocks)
{
    sanity-checks ();
    for-each block in blocks {
        if (block is-in md-device)
            md_map (block)
    }
    for-each block in blocks {
        make_request (block);
    }
}

块重映射发生在输入/输出请求被放入系统请求队列之前。这个重映射函数非常简单。它使用指向设备和块号的指针调用,它所做的只是更改设备 ID 和块号。设备 ID 被更改为指向磁盘阵列中的一个磁盘,块号被更改为指向该磁盘上的正确位置。基本上,这是一个不错的技巧(但它使用了一些“ifdefs”,我们都知道我们无畏的领导者不喜欢)。

RAID-1、4、5 的扩展

MD 映射函数对于重新映射线性特性和条带化特性等特性工作良好。不幸的是,它不足以支持 RAID-1、RAID-4 和 RAID-5 特性。原因是这些模式不能仅仅通过将给定的设备/扇区对重新映射到新的设备/扇区对来完成它们的工作;所有这些新特性都需要复杂的输入/输出操作才能满足单个请求。

例如,镜像特性 (RAID-1) 将信息复制到阵列中的所有磁盘上,以便每个磁盘上的信息都相同。磁盘阵列的大小是最小磁盘的大小(最小公分母)。因此,使用 100MB 磁盘和 1GB 磁盘制作 RAID-1 不是一个好主意,因为驱动程序只会为每个磁盘提供 100MB(并忽略您在第二个磁盘上有 900MB 可用的事实)。

当被要求将信息写入设备时,MD 驱动程序会为阵列中所有具有相同信息的设备排队一个写入请求。系统可以使用所有剩余的设备(通常,只有一个额外的设备)继续正常运行。在镜像到位的情况下,读取请求在阵列中的设备之间进行平衡。如果任何设备发生故障,设备驱动程序会将其标记为不可操作并停止使用它;驱动程序将继续使用剩余的磁盘工作,就像什么都没发生一样。图 2 显示了如何使用此模式;阴影区域代表冗余信息。

The Linux RAID-1, 4, 5 Code

接下来我们有更复杂的块交织奇偶校验 (RAID-4) 和块交织分布式奇偶校验 (RAID-5) 特性。条带化单元是一组连续扇区。RAID-4 和 RAID-5 特性都使用阵列中一个磁盘上的一个条带来存储奇偶校验信息,并使用剩余的条带来存储数据。

每当修改其中一个数据扇区时,都必须重新计算奇偶校验扇区并将其写回磁盘。为了重新计算奇偶校验扇区,设备驱动程序必须读取旧奇偶校验块的内容并重新计算新的奇偶校验信息。然后,它写入数据和奇偶校验块的新内容。

RAID-4 和 RAID-5 之间的区别在于,前者将奇偶校验信息存储在固定设备中(在我们实现中的最后一个组成设备),而后者在组成设备之间分配奇偶校验块。

图 3 显示了 RAID-4 的排列方式;阴影区域是冗余信息,在这种情况下是奇偶校验块。RAID-4 由于奇偶校验磁盘而存在瓶颈,因为所有 MD 活动都依赖于奇偶校验位磁盘来完成其操作。

The Linux RAID-1, 4, 5 Code

图 4 显示了 RAID-5 的排列方式;阴影区域是冗余信息(再次是奇偶校验块),这里的每个磁盘代表一个条带化单元。在 RAID-5 中,RAID-4 瓶颈通过在多个磁盘之间分配工作来消除。

The Linux RAID-1, 4, 5 Code

如果在使用 RAID-4 或 RAID-5 特性时发生磁盘故障,则磁盘阵列可以继续运行,因为它有足够的信息来重建任何丢失的磁盘。此时 MD 设备速度较慢;每次访问丢失磁盘中的数据扇区都需要从同一条带中的所有扇区以及奇偶校验位读取信息,并根据此信息计算原始扇区的内容。

支持新特性的内核更改

正如我们所实现的,新特性需要更改 ll_rw_blk 例程,并在内核的输入/输出结束请求通知代码中添加一些额外的代码。

修改了 ll_rw_blk 例程,以便在计划将块发送到 MD 设备时,调用 md_make_request 而不是调用内核的 make_request。md_make_request 例程反过来为每个特性调用一次请求生成例程以执行其工作。在 RAID-0 和线性模式的情况下,md_make_request 调用常规的 make_request 例程。

在存在新的 MD 驱动程序的情况下,ll_rw_blk 例程如下所示

ll_rw_blk (blocks)
{
    sanity-checks ();
    for-each block in blocks {
        if (block is md-device)
            md_map (block)
    }
    for-each block in blocks {
        if (request-is-for (raid1,raid4,raid5))
            md_make_request (block);
        else
            make_request (block);
    }
}

由于新的 RAID 代码的复杂性和错误恢复能力增强,必须将特性代码告知其输入/输出操作的结果。如果磁盘发生故障,则必须将磁盘标记为不可操作,并且在 RAID-4 和 RAID-5 代码的情况下,它必须在进程的不同阶段之间移动。我们修改了内核输入/输出结束请求例程,以调用 RAID 特性的 end_request 例程来处理结果。

一个典型的 end_request 例程如下

raidx_end_request (buffer_head, uptodate)
{
    if (!uptodate)
        md_error (buffer_head)
    if (buffer_head->cmd is READ){
        if (uptodate){
            raidn_end_buffer_io (buffer_head,
                uptodate)
            return;
        /* read error */
        raid_reschedule_retry (buffer_head);
    }
    /* write case */
    if (finished-with buffer_head)
        raidn_end_buffer_io (buffer_head,
                uptodate)
}
通用 MD 更改

我们希望保留 ll_rw_blk 例程的行为,使其尽可能接近此例程的客户端代码所期望的行为。由于代码现在提供错误恢复,因此一个看似无辜且简单的请求实际上可能变成一组复杂的请求;因此,MD 代码现在启动可配置数量的内核线程,用于仲裁复杂的请求。它通过 md_register_threadmd_unregister_thread 函数将这些线程导出到新特性。MD 线程各自在其自己的等待队列上休眠,并在需要时唤醒。然后,它们调用特性的线程并运行磁盘任务队列。

RAID-1 代码

镜像特性是新代码中最简单的特性。每当发出读取请求时,它都会被发送到磁盘阵列中的一个可操作磁盘;在写入请求的情况下,特性的请求生成代码会将阵列中每个设备的写入请求放入系统请求队列中。

如果在写入时其中一个设备发生磁盘错误,则该设备被标记为不可操作,并且消息被记录到 syslog 工具以通知操作员有关情况。如果此错误发生在磁盘读取期间,则代码会尝试从一个可操作设备重新读取;然后它将读取请求放入队列并唤醒 raid1d 内核线程。

raid1d 内核线程在其生命周期中只有一个目的——重试读取请求。当它醒来时,它会重试对所有可操作磁盘的任何排队请求。

RAID-4 和 RAID-5 代码

RAID-4 和 RAID-5 都提供块交织奇偶校验;前者将奇偶校验存储在阵列中的单个磁盘中,而后者在阵列中的所有磁盘之间分配奇偶校验。两种模式下的大部分代码是相同的。只有一个例程使两种模式之间存在差异。

最简单的代码路径是阵列的所有磁盘都正常工作的情况。在这种情况下,读取和写入模式有两种代码路径。当被要求从磁盘阵列读取块时,代码将数据扇区的计算位置放入系统请求队列中,并且不会出现进一步的复杂情况。

在写入的情况下,事情变得更加复杂,因为驱动程序必须写入相应的块以及更新奇偶校验块。当写入磁盘阵列中的单个扇区时,代码需要执行以下操作

  1. 读取数据扇区的旧内容和奇偶校验扇区的旧内容。

  2. 计算新的奇偶校验扇区。

  3. 写入带有新奇偶校验扇区的新数据扇区。

由于上层期望代码将请求放在队列中,因此代码启动磁盘上的读取请求并返回给调用者。

当任何请求完成时,将调用 raid5_end_request。此例程与 RAID-5 线程 raid5d 一起负责跟踪块的当前状态。如果磁盘 I/O 操作没有问题,则请求最终被标记为已完成,上层可以使用该块。

如果在块读取或写入期间发生错误,RAID 驱动程序会将故障磁盘标记为不可操作磁盘,并在不使用它的情况下继续操作。驱动程序会重建丢失的块,并根据其他磁盘上可用的信息计算奇偶校验。在出现问题的情况下,块读取和块写入例程都变得更加复杂。磁盘阵列速度较慢,但系统的正常运行可以继续,直到更换故障磁盘。

如果在磁盘阵列上配置了备用磁盘,RAID 驱动程序会启动一个线程,该线程重新创建发生故障的磁盘上的信息。当它完成重建后,磁盘被标记为可操作,并且驱动程序以正常速度恢复操作。

其他更改

我们已经更新了控制 MD 驱动程序的“用户空间”实用程序,以利用新特性中发现的功能。

工具 mkraid 用于配置 RAID 设备。它读取配置文件并创建一个 RAID 超级块,其中存储了有关设备的所有管理信息。

在系统启动时,syncraid 程序检查 RAID 超级块,以确保 RAID 系统已干净卸载,并在出现问题时重建冗余信息。

RAID-4 中存在一个明显的瓶颈。所有奇偶校验信息都保存在单个磁盘上,因此在磁盘阵列中的任何数据磁盘上完成的任何写操作都将导致写入奇偶校验磁盘。因此,磁盘阵列级别 4 的速度限制受到奇偶校验磁盘速度的限制。拥有这样的特性似乎是不合理的,因为 RAID-5 解决了这个问题。我们之所以实现 RAID-4,是因为在某些磁盘阵列配置中,对磁盘的访问可能会被磁盘控制器序列化(某些 IDE 驱动器就是这种情况)。

鸣谢

未来工作

我们希望提供一个 RAID-1+ 特性,它基本上是 RAID-1 加上检查点;这将使我们能够在磁盘崩溃后在启动时同步磁盘。不幸的是,PC 计算机没有配备 NVRAM 设备,这将使我们能够以最少的工作量完成此操作。

可以增强 RAID-4 和 RAID-5 代码,使其能够确定何时将完整的条带写入磁盘。当检测到这种情况时,驱动程序不需要读取磁盘上找到的任何旧信息。我们可以按请求队列中的数据扇区原样写入所有数据扇区,计算新的奇偶校验位扇区并将其添加到队列中。这是提高 RAID-4 和 RAID-5 磁盘写入速度的首选方法,如“RAID 5 磁盘阵列中的条带化”(请参阅资源)中所述。

目前,低级、请求队列和排序代码使用电梯算法进行写入。该领域的初步工作表明,可以通过了解磁盘几何形状的算法以每个设备的方式改进该算法。以每个设备的方式虚拟化请求队列的排序算法可能会带来良好的性能提升。

当系统崩溃时,RAID-4 和 RAID-5 系统需要重新计算磁盘阵列上的奇偶校验信息,因为奇偶校验块可能包含旧信息。为了改善这种情况,我们希望利用非易失性 RAM(在那些具有此类硬件的系统上)来检查磁盘阵列的状态。借助此功能,我们可以重新计算任何可能在启动磁盘阵列时包含不正确信息的块的信息。

资源

Gadi Oxman (gadio@netvision.net.il) 是 Linux IDE/ATAPI 磁带和软盘驱动程序以及 ext2ed(Linux ext2 文件系统的文件系统编辑器)的作者。

Ingo Molnar (mingo@vger.rutgers.edu) 是多个黑客类型的内核补丁的作者;他是 Linux 内核“必读”人物。

Miguel de Icaza (miguel@gnu.ai.mit.edu) 是 GNU Midnight Commander 的作者之一。他还参与了 Linux/SPARC 内核端口的工作。

加载 Disqus 评论