嵌入式Linux中原NAND闪存存储预读效率的再探讨
摘要
Linux预读机制旨在弥合二级存储性能低下与个人计算机和服务器中I/O读取密集型应用之间的差距。本文重新审视了该机制在使用闪存作为二级存储的嵌入式Linux系统中的效率,而这种情况在大多数嵌入式系统中普遍存在。实际上,Linux内核在不同应用领域均采用相同的预读机制。本文从响应时间和能耗角度,评估了该预读技术在广泛使用的闪存专用文件系统JFFS2和YAFFS2上的效率。我们通过微基准测试,在细粒度(系统调用)层面分析预读对这些指标的影响。此外,还利用宏基准测试在更高应用级别研究预读对SQLite数据库管理系统读取性能和功耗的影响。如本文所述,在顺序模式下禁用该机制可使性能和能耗改善最高达70%,在随机模式下最高可达60%。
分类与主题描述
D.4.2[操作系统]:存储管理–辅助存储、主存、存储层次结构。
D.4.3[操作系统]:文件系统管理–访问方法。
D.4.8[操作系统]:性能–测量、 modeling与预测。
通用术语
性能,测量,设计。
关键词
嵌入式Linux,闪存文件系统,JFFS2,YAFFS2,Linux页缓存,预读
1. 引言
嵌入式Linux已成为许多嵌入式应用程序(如消费类电子产品(智能手机、平板电脑等)、多媒体设备和机顶盒)的事实操作系统。它也被集成在路由器、视频监控系统和机器人等多种设备中。对于Linux而言,并没有专门为嵌入式系统设计的特殊内核形式,而是采用一个统一的(可配置的)内核,适用于最广泛的设备范围。嵌入式Linux的一个特点是使用特殊的子系统,通过闪存文件系统(FFS)来管理基于裸闪存的存储系统。
NAND闪存市场的激增通过提供高效且相对便宜的非易失性存储器(NVM),推动了许多嵌入式系统应用的发展,尤其是消费类电子产品。事实上,移动存储器(包括NOR和NAND闪存、DRAM以及嵌入式多媒体卡)市场在2012年相比2011年增长了14%。然而,在设计嵌入式系统时,NAND闪存存在一些必须应对的特殊限制:(1)写/擦除粒度不对称:写操作以页为单位进行,而擦除操作则以块为单位执行,一个块由多个页组成。(2)先擦除后写入规则:无法就地修改数据。如果需要在同一位置更新数据,则必须先执行代价较高的擦除操作,然后才能修改数据。(3)有限的写/擦除(w/e)次数:根据所使用的闪存技术不同,平均寿命在5000到10^5次之间。当达到最大擦除次数后,特定的存储单元将变得不可用。最后,(4)读写操作的I/O性能不对称。
有两种可能的方法来应对上述闪存限制:(1)通过包含在存储设备本身的硬件/软件控制器,即闪存转换层(FTL)。U盘、闪存卡、固态硬盘等属于这种情况;或(2)通过在嵌入式操作系统层面实现的特定闪存文件系统(FFS),以纯软件方式解决。JFFS2[1]、YAFFS2[2]和UBIFS[3]是最流行的FFS。它们都依赖于内核中的一个底层模块,该模块用于连接文件系统与闪存驱动程序:内存技术设备(MTD)。该软件栈的行为不同于传统的块设备和文件系统,因此与上层内核文件管理模块的交互也大不相同。Linux预读算法即实现在这些层次中,其目的是预取从二级存储读取的数据,以提升进程的I/O读取性能。
本文研究分析了嵌入式Linux上基于闪存的存储系统与页面缓存预读预取系统之间的相互作用。实验结果表明,顺序和随机I/O工作负载均出现显著性能下降。这导致中央处理器以及内存子系统(随机存取存储器和闪存)产生额外的能耗开销。本文试图量化这一行为。
本文的组织结构如下:第一节介绍闪存的一些背景知识。第3节描述性能评估方法。第4节讨论获得的结果。第5节给出一些结论。
2. 预备知识
2.1 闪存管理机制
为了应对上述NAND闪存的限制,实现了一些特定的管理机制。(1)为了避免代价高昂的原地数据更新,采用了一种逻辑到物理地址映射机制,以支持异地数据修改(在其他位置更新数据并使原始副本无效)。(2)由于写/擦除(w/e)周期数量有限,并且由于I/O工作负载具有空间和时间数据局部性,包含“热点”数据的某些特定块可能会迅速磨损。为避免此问题,在映射系统中实现了磨损均衡机制,以在整个内存表面上均匀分布擦除操作。(3)执行大量更新操作会产生大量无效页/块,这些页/块必须被擦除后才能重新使用。为此使用了垃圾回收器来完成该任务。
图1展示了FFS层在Linux存储层次结构中的位置。用户空间进程通过系统调用访问文件,这些系统调用由虚拟文件系统(VFS)接收。VFS的作用是抽象底层文件系统的特性。此外,在VFS层,Linux在RAM中维护了多个缓存以加速文件操作。特别是,Linux页缓存专门用于文件数据缓冲。VFS将系统调用映射到FFS功能。为了访问闪存芯片,FFS使用NAND驱动程序MTD。
硬盘驱动器在随机I/O请求上表现出较差的性能。这是由于存在产生较大延迟的机械部件所致。Linux预读[4]机制的设计旨在缓解这一问题。它允许从磁盘中顺序预取比进程访问文件时所请求更多的数据。预取数据的大小基于复杂的启发式算法,其目的是判断进程的全局读取模式是顺序还是随机。对于顺序访问模式,预读会预取大量数据;而对于随机访问模式,则关闭预读。
读取较小的数据量。预读在VFS层实现:它独立于底层文件系统。该机制通过以下方式提升基于磁盘的存储系统的I/O性能:[4]:(1)通过读取较大的数据块来减少机械运动;(2)依赖I/O超时来异步预取数据。因此,在页面缓存命中的情况下,数据直接从页面缓存中读取,无需访问二级存储。
2.2 动机示例
由于闪存与硬盘驱动器在本质上存在差异,人们可能会质疑预读在基于闪存的存储中的实用性。在Linux中,默认情况下启用了预读功能,除非文件系统本身指定不使用该功能。这可以在文件系统的源代码中实现。
作为初步实验,我们使用了流行的存储基准测试工具Postmark[5]。在基准测试执行过程中,每次读取文件时,我们都测量了read()系统调用所花费的时间,然后计算了平均读取吞吐量。吞吐量测量在每次文件读取操作的三个时刻进行:当文件读取进度达到25%、50%和100%时。该基准测试在一个硬件平台(本文后续将描述的基于TI OMAP的开发板)上运行,Linux系统分别在(A)预读开启和(2)预读关闭两种情况下进行测试。结果如表1所示。
| 百分比文件大小的read | FFS: JFFS2 RA 开启 | FFS: JFFS2 RA 关闭 | FFS: YAFFS2 RA 开启 | FFS: YAFFS2 RA 关闭 |
|---|---|---|---|---|
| 25% | 2.86 | 6.68 | 6.68 | 14.30 |
| 50% | 4.77 | 7.63 | 9.54 | 14.30 |
| 100% | 7.63 | 7.63 | 14.30 | 14.30 |
JFFS2和YAFFS2之间的性能差异可能是由于YAFFS2内部缓存系统[2],以及JFFS2在解压缩构成Postmark文件的随机数据时浪费了时间所致。这些结果表明,在读取文件时,如果未完全读取该文件,预读将导致显著的性能下降。本文详细研究并分析了这种影响对流行的FFS JFFS2和YAFFS2的性能和功耗的影响。
2.3 相关工作
基于闪存读取操作与模式无关(顺序读取和随机读取之间无差异)这一事实,UBIFS中禁用了预读功能。然而,关于此问题尚未发表实验评估。在[6]中,作者提出了一种用于嵌入式系统中闪存管理的算法,由于性能开销,该算法禁用了预读功能。在[7]中,提出了一种新颖的预读算法,以提升在闪存上压缩文件系统(CramFS)按需分页环境中的预读性能。
3. 嵌入式Linux上预读取的性能评估
3.1 方法与指标
3.1.1 微基准测试
所进行的实验包括测量在启用和禁用预读的情况下,JFFS2和YAFFS2上顺序和随机I/O工作负载的性能和能耗。我们首先修改了JFFS2和YAFFS2的源代码以禁用预读功能。需要注意的是,该机制也可以在应用程序级别通过使用posix_fadvise()系统调用来禁用,而无需修改内核源代码。这是一种侵入性较小但通用性较差的解决方案。我们设计了一个简单的C测试程序,在存储于JFFS2或YAFFS2闪存分区中的目标文件上循环执行读取操作。读取操作通过read()系统调用完成,并使用O_RDONLY标志打开目标文件。在测试程序中,多个参数被改变:生成的读请求数量、请求大小、目标文件大小、到达间隔时间以及访问模式(随机或顺序)。每次测试前,都会清空Linux页缓存,以确保初始状态一致。
性能评估 :使用gettimeofday()系统函数测量每次read()系统调用的执行时间,精度达到微秒级。我们还使用Flashmon[8]来跟踪每次测试中的闪存I/O访问次数,以比较启用和不启用预读机制时生成的读取操作次数。
功耗 :为了研究预读机制对能耗的影响,我们在进行读取访问时测量了(1)中央处理器和(2)随机存取存储器+闪存内存的电源轨(在我们的硬件测试平台上,随机存取存储器和闪存共享同一电源轨)上的功耗。这使我们能够建立一个简单的功耗模型,以估算预读对I/O能耗的影响。该模型的基本思路是将读取访问期间测得的平均功率乘以特定实验的执行时间,从而得到能耗。
3.1.2 SQLite宏基准测试
SQLite[9]是一种使用SQL查询语言的关系型数据库引擎。其特点之一是,SQLite数据库完全包含在常规文件系统之上的一个单个文件中。此外,SQLite数据库管理系统有多种提供形式,特别是以C库的形式存在,可直接嵌入(即与)应用程序一起编译,从而无需任何外部依赖。由于其良好的可移植性、相对较低的CPU负载和内存占用,以及对突然断电的容忍能力,SQLite被广泛应用于嵌入式系统中。特别是,SQLite是用于在安卓嵌入式操作系统中管理系统和用户数据库[10]。
我们使用包含单个表的SQLite数据库进行宏基准测试实验。与[10]中一样,我们采用了一种模拟安卓操作系统联系人数据库的模式。该联系人表包含17个字段,其中12个为整数类型,5个为文本字段。其中一个整数字段是唯一的记录标识符(主键)。该数据库创建在先前已擦除的JFFS2和YAFFS2闪存分区上,并填充了1000条包含随机数据的记录。我们创建了两个版本的数据库,一个版本中每个文本字段填充64字节的字符串,另一个版本中填充128字节的字符串。数据库文件报告的大小分别为第一版518千字节,第二版1兆字节。在本文其余部分,我们将第一个版本称为小型数据库,第二个版本称为大型数据库。
性能评估 :我们创建了一个集成SQLite库并对数据库执行选择操作的C应用程序。使用该应用程序,我们进行了多次选择运行,每次运行包括在循环中从数据库进行一次或多次记录选择。根据读取记录标识符的顺序(记录标识符在表创建期间递增分配),运行可以以顺序或随机模式执行。每次运行前都会清除Linux页缓存,并使用gettimeofday()测量每次运行的执行时间。实验在预读开启和关闭的情况下分别进行。
功耗 :我们使用在微基准测试阶段创建的功耗模型的改进版本,来估算预读对SQLite选择期间能耗的影响。
3.2 硬件和软件实验配置
我们使用了一块Mistral Omap3evm开发板,该开发板嵌入了OMAP3530(720 MHz ARM Cortex A8)中央处理器、256兆字节随机存取存储器和256兆字节美光SLC(单层单元)NAND闪存。NAND芯片的数据手册显示,读取一个2048字节的闪存页(包括内部读取操作及在I/O总线上的传输)的延迟为130微秒。如前所述,随机存取存储器和闪存共享同一电源轨。实验采用Linux内核2.6.37,并使用标准的嵌入式内核配置。功耗测量方面,我们使用了Open-PEOPLE(开放式功耗与能量优化平台及估算器)平台[11][12],该平台配备了美国国家仪器PXI-4472模块。宏基准测试中采用了SQLite的3.7.15.2版本。
4. 结果与讨论
4.1 微基准测试:读系统调用性能
4.1.1 到达间隔时间的影响:为何预读在FFS上表现不佳
使用磁盘时,可以在I/O超时期间异步启动预读机制,以优化I/O响应时间。相比之下,FFS使用的Linux NAND驱动(MTD)是一个完全同步的软件栈。我们通过重放相同的实验、顺序读取5MB文件的一部分并插入读取之间的到达间隔时间来测试此功能。
图2显示了在顺序工作负载下启用预读时,随着到达间隔时间变化的平均读取延迟。响应时间不受到达间隔时间增加的影响,这证实了闪存操作的同步特性。事实上,当启用预读时,预取由页面缓存触发的请求是同步处理的,因此会延迟应用程序读取响应时间。换句话说,由于数据预取导致的I/O延迟会被添加到触发预读过程的read()系统调用执行时间中。由于在内存技术设备上预取是同步进行的,预读无法对调用进程屏蔽I/O延迟。因此,在最佳情况下(所有预取数据都被使用),预读并不能提升进程的读取性能。此外,当预取数据未被使用时,预读只可能对性能产生负面影响。正因如此,本文中大多数曲线图展示的是禁用预读后获得的性能/功耗改善情况。
4.1.2 请求数量的影响
我们在一个5MB目标文件上,针对固定的4KB请求大小,将请求数量从8变化到1024,且I/O请求之间没有到达间隔时间。对于最大的请求数量,大部分文件被读取。图3-a显示了在禁用预读时总I/O响应时间的改进率。
可以观察到,禁用预读始终能够提升性能。此外,JFFS2和YAFFS2的行为相似,因为它们在顺序和随机工作负载下的表现相同。事实上,预读的影响并不依赖于所使用的文件系统,因为该技术是在底层实现的(上)VFS层。预读只是向文件系统请求更多数据,从而导致更多的闪存读取。这一点可以通过图3-b验证,该图显示了相同实验中生成的闪存读取次数的结果(通过Flashmon跟踪)。
在顺序工作负载下,我们可以注意到,请求的数量越少,禁用预读带来的性能提升越好(当对同一文件的请求数量少于64时,可观察到超过50%的闪存操作减少)。事实上,在顺序工作负载下,预读非常活跃,并发出大量预取请求。由于预取请求的大小是稳定的(在我们的实验中约为128千字节),当应用程序读取请求的数量较小时,预取开销相对较高(因为并非所有预取数据都会被使用)。另一方面,当请求数量较多时,更多的预取数据会被使用,从而导致更小的开销。
在随机工作负载下,禁用预读机制时也可以观察到性能提升。这意味着预读机制实际上被激活用于随机工作负载。我们可以注意到,请求数量较小时(最多32个请求),性能提升并不显著(约20%的提升)。事实上,预读不会预取大量数据块。当请求数量达到256时,性能提升最高可达50%。在随机工作负载下,除非总访问空间足够大(相对于目标文件大小)以体现时间局部性,否则预取数据不太可能在不久的将来被访问。当请求数量从256增加到1024时,正是这种情况:当总寻址空间较大时,我们观察到大量的页面缓存命中。
最后可以得出的一个普遍观察结果是:在读取整个文件时,禁用预读机制带来的性能提升极小,这是因为预取数据中没有浪费(应用程序读取了所有预取的数据)。
4.1.3 请求大小的影响
在32 MB目标文件上,我们改变了请求数量和请求大小。结果如图4-a所示。
在对于顺序工作负载,在请求数量固定且较小的情况下,我们观察到,随着请求大小的增加,禁用预读带来的性能提升会降低。这种行为与改变请求数量时的观察结果类似。实际上,预读的影响取决于读取的总空间(请求数量乘以请求大小)。
在随机工作负载下,性能优化相对稳定,除了较大的寻址空间(1024个32千字节请求)之外。这是由于当寻址空间接近总文件大小时,产生了大量的页面缓存命中所致。在较小的寻址空间下,性能提升较低,因为预读仅针对小规模的随机工作负载预取少量数据。最后,可以观察到2千字节请求相比4千字节请求(内存页大小)有轻微的性能提升。这可能是由于内存对齐问题所致。
4.2 微基准测试:能耗估算
我们观察到,影响中央处理器功耗的参数是文件系统类型以及预读功能的开启或关闭。除了这些参数外,访问模式也会影响内存功耗。结果如表2所示。
| Component | File System | Access Mode | Prefetch | Power During Read (W) |
|---|---|---|---|---|
| CPU | JFFS2 | no impact | ON | 0.223 |
| CPU | JFFS2 | no impact | OFF | 0.223 |
| CPU | YAFFS2 | no impact | ON | 0.211 |
| CPU | YAFFS2 | no impact | OFF | 0.214 |
| Memory (RAM + Flash) | JFFS2 | SEQ | ON | 0.029 |
| Memory (RAM + Flash) | JFFS2 | SEQ | OFF | 0.024 |
| Memory (RAM + Flash) | JFFS2 | RAN | ON | 0.023 |
| Memory (RAM + Flash) | JFFS2 | RAN | OFF | 0.023 |
| Memory (RAM + Flash) | YAFFS2 | SEQ | ON | 0.028 |
| Memory (RAM + Flash) | YAFFS2 | SEQ | OFF | 0.028 |
| Memory (RAM + Flash) | YAFFS2 | RAN | ON | 0.024 |
| Memory (RAM + Flash) | YAFFS2 | RAN | OFF | 0.026 |
功耗通过从读取期间测得的功耗中减去空闲功耗(执行sleep命令时测得的功耗)获得。我们观察到这些值足够稳定,可以提取出一个简单的能耗模型。
如图5所示,能耗由一个方形区域表示。无论是否开启预读,功耗的大小均不发生变化。因此,能耗仅受响应时间值的影响。能耗模型可近似如下:
$$ E_{total} = t_{exp} \times (P_{CPU} + P_{Mem}) $$
$E_{total}$ 是实验中读取操作消耗的总能耗,$t_{exp}$ 是读取操作的执行时间,$P_{CPU}$ 和 $P_{Mem}$ 是读取操作期间平均功耗的估计值。
图4-b展示了能耗的结果,其表现出与性能结果非常相似的行为。事实上,读请求的执行时间是影响能耗的主要因素。正如表2所示,平均功耗的变化非常轻微。
4.3 预读对SQLite数据库读取性能和功耗的影响
在本节中,我们测量了预读对SQLite数据库性能的影响,并调整了先前提出的模型,以估算预读对数据库访问功耗的影响。
4.3.1 性能影响测量
关于微基准测试结果,无论文件系统类型、访问模式和数据库大小(小/大)如何,禁用预读总是会减少SQL SELECT的执行时间。因此,我们再次绘制了通过禁用预取机制获得的性能提升。性能结果如图6所示。
可以看出,顺序模式下的性能提升相对稳定,保持在10%左右。综合小数据库和大数据库的结果,JFFS2的性能提升平均值为9.5%,YAFFS2为5%。我们并未观察到上一节中测量到的顺序工作负载下的显著性能提升。这可能表明大多数预取数据实际上被使用了。还需要注意的是,顺序选择记录并不一定转化为顺序文件读取操作:这种行为取决于记录的插入/更新方式,以及更普遍的SQLite内部算法。
在随机工作负载下,可以看出预读会产生较大的开销:JFFS2的性能提升最高可达70%,YAFFS2为50%。JFFS2和YAFFS2的平均值分别为44%和25%。在随机模式下,当单次运行中读取的记录数增加时,性能提升会下降。在这种情况下,根据访问的性质,文件的大部分内容会从小量开始从闪存中读取并预取。因此,页面缓存命中的数量增加,预读调用的频率降低,从而使其影响最小化。这取决于运行中SELECT的记录数以及数据库文件的总大小。
4.3.2 功耗影响估计
采用与微基准测试阶段相同的方法,我们在启用/禁用预读的情况下,对JFFS2和YAFFS2文件系统执行大量SELECT操作时的平均功耗进行了测量。结果如表3所示。我们未改变访问模式,因为此前已证明其对功耗影响较小。
| Component | File System | Prefetch | Power During SELECT (W) |
|---|---|---|---|
| CPU | JFFS2 | ON | 0.24565 |
| CPU | JFFS2 | OFF | 0.23925 |
| CPU | YAFFS2 | ON | 0.245814 |
| CPU | YAFFS2 | OFF | 0.245296 |
| Memory (RAM + Flash) | JFFS2 | ON | 0.030188 |
| Memory (RAM + Flash) | JFFS2 | OFF | 0.036863 |
| Memory (RAM + Flash) | YAFFS2 | ON | 0.033325 |
| Memory (RAM + Flash) | YAFFS2 | OFF | 0.036319 |
平均功耗乘以前一节中给出的SQLite SELECT执行时间测量值。图7表示通过禁用预读所获得的估计节能效果。由于测得的各项功耗数值变化较小,执行时间是能耗方程中的主要影响因素。因此,节能图表与图6中所示的运行时间改进非常相似。综合考虑大、小数据库配置,JFFS2的平均节能分别为8.6%(seq.)和41%(ran.)。对于YAFFS2,这些数值分别为5.3%和25.5%。
5. 结论
由于嵌入式Linux中NAND驱动程序的同步特性,预读对基于原始NAND闪存的嵌入式存储系统的性能和功耗均产生负面影响。我们研究了该效应在JFFS2和YAFFS2闪存文件系统上的表现。根据微基准测试结果表明,影响预读效果的主要参数是访问模式以及进程读取文件的比例。在我们的测试平台上,禁用该技术可使性能和能耗最多改善70%。我们还发现,在SQLite SELECT操作中,尤其是在随机工作负载下,禁用预读可显著提升性能并降低功耗。
用于在JFFS2/YAFFS2中禁用预读的内核补丁可从以下URL获取:http://syst.univ-brest.fr/~pierre/Files/ra_ffs.zip。

432

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



