跳到正文
Qiita · Unity 开发社区· 来源· hk816·· 3 小时前AI 评分1

基于 Unity 测试框架的 SSD L2P 映射更新原子性与健壮性验证设计

【テスト】SSDの文鎮化をテストで防げ!「L2Pマッピング更新」の完全網羅テスト設計(Unity編)

正文 · AI 翻译

0

转到点赞用户列表

0

在 X (Twitter) 上分享

在 Facebook 上分享

添加到 Hatena Bookmark

删除文章

已删除的文章无法恢复。

本文的草稿也会被删除。

您确定要删除这篇文章吗?

\n🚀 用测试防止 SSD 变砖!「L2P 映射更新」的全面测试设计(Unity 篇)

各位嵌入式工程师,在 SSD 固件中,“数据损坏”的瞬间是什么时候呢?
那不是 NAND 闪存寿命耗尽的时候。而是“正在向 NAND 写入新数据时电源突然断开,而管理表(L2P)却先被更新时”。

为了预防这种情况,证明正确实现了“Write-then-Update(事务的原子性)”的单元测试将在本次介绍。
利用 Unity(面向 C 语言的测试框架)和 Mock,故意引发“NAND 写入失败(Program Fail)”这一硬件的最坏行为,从而 100% 证明固件的健壮性!


\n💡 测试设计的要点(面试中能谈到的测试精髓)

在本测试组中,不仅旨在提高代码覆盖率(Coverage),还专门针对存储特有的“边缘情况”进行测试。

  1. 原子性的完整证明(异常场景)
    让模拟函数 HAL_NandProgramPage 返回错误(false)。在此“写入失败”之后,验证 DRAM 上的 L2P 表绝对未被重写为新的 PBA,而是保持旧的 PBA(回滚保护)。
  2. 坏块隔离(GBB 注册)的验证(异常场景)
    由于向 NAND 写入失败的物理块很可能在物理上已损坏,因此确认该块是否立即在 FTL_MarkBlockAsGbb 中被隔离(注册为坏块)。
  3. 覆盖写入时的“旧 PBA 无效化”验证(正常场景)
    针对首次写入(Unmapped)和第二次及以后的覆盖写入(Overwrite)进行分支测试。证明仅在覆盖写入成功时,确保对存储有旧数据的旧 PBA 调用 FTL_InvalidatePba,使其成为垃圾回收(GC)的对象。
  4. 防御性编程的验证(异常场景)
    验证当输入 NULL 指针或超出规格上限的 LBA(越界)时,会在引发内存破坏之前立即被拒绝。

💻 实现代码

#include "unity.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* テスト対象のCファイルを直接インクルード(ホワイトボックステスト用) */
#include "ftl_l2p_update.c"

/* --- モック用制御・状態記録変数 --- */
static bool     g_mock_allocate_success = true;
static uint32_t g_mock_next_free_pba = 0U;

static bool     g_mock_program_success = true;
static uint32_t g_call_program = 0U;

static uint32_t g_call_invalidate = 0U;
static uint32_t g_last_invalidated_pba = FTL_UNMAPPED_PBA;

static uint32_t g_call_gbb = 0U;
static uint32_t g_last_gbb_pba = FTL_UNMAPPED_PBA;

/* --- 外部HAL/FW関数のダミー実装 (モック) --- */
bool FTL_AllocateFreePba(uint32_t* p_out_pba) {
    if (g_mock_allocate_success) {
        *p_out_pba = g_mock_next_free_pba;
        g_mock_next_free_pba++;
        return true;
    }
    return false;
}

bool HAL_NandProgramPage(uint32_t pba, const uint8_t* p_data, const NAND_OobMetadata_t* p_oob) {
    g_call_program++;
    return g_mock_program_success;
}

void FTL_InvalidatePba(uint32_t pba) {
    g_call_invalidate++;
    g_last_invalidated_pba = pba;
}

void FTL_MarkBlockAsGbb(uint32_t pba) {
    g_call_gbb++;
    g_last_gbb_pba = pba;
}

/* --- テストのセットアップ --- */
void setUp(void) {
    g_mock_allocate_success = true;
    g_mock_next_free_pba = 1000U; /* テスト用の開始PBA */
    g_mock_program_success = true;
    
    g_call_program = 0U;
    g_call_invalidate = 0U;
    g_last_invalidated_pba = FTL_UNMAPPED_PBA;
    g_call_gbb = 0U;
    g_last_gbb_pba = FTL_UNMAPPED_PBA;
    
    /* L2Pテーブルを未マッピング状態で初期化 */
    for (uint32_t i = 0U; i < FTL_MAX_SUPPORTED_LBA; i++) {
        s_l2p_table[i] = FTL_UNMAPPED_PBA;
    }
}
void tearDown(void) {}

/* =========================================================
 *  FTL_WriteAndMap のテストケース
 * ========================================================= */

/* 1. 異常系: 引数エラー(NULLポインタと境界値) */
void test_FTL_WriteAndMap_InvalidArgs_ShouldReturnFalse(void) {
    uint8_t data[4096] = {0};
    
    TEST_ASSERT_FALSE_MESSAGE(FTL_WriteAndMap(0U, NULL), "データポインタがNULLの場合はfalseを返すこと");
    TEST_ASSERT_FALSE_MESSAGE(FTL_WriteAndMap(FTL_MAX_SUPPORTED_LBA, data), "上限以上のLBAを指定した場合はfalseを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_program, "無効な要求ではNANDアクセスを行わないこと");
}

/* 2. 異常系: 空き物理ブロックが枯渇している場合のアロケーション失敗 */
void test_FTL_WriteAndMap_AllocationFails_ShouldReturnFalse(void) {
    uint8_t data[4096] = {0};
    g_mock_allocate_success = false; /* SSDフル等の理由でPBA取得失敗をシミュレート */
    
    bool result = FTL_WriteAndMap(10U, data);
    
    TEST_ASSERT_FALSE_MESSAGE(result, "PBA確保失敗時はfalseを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_program, "NAND Programは呼ばれないこと");
}

/* 3. 正常系: 未マッピング領域への新規書き込み */
void test_FTL_WriteAndMap_FirstWrite_ShouldMapSuccessfully(void) {
    uint8_t data[4096] = {0xAA};
    uint32_t target_lba = 100U;
    uint32_t expected_pba = 1000U; /* モックが返す最初のPBA */
    
    bool result = FTL_WriteAndMap(target_lba, data);
    
    TEST_ASSERT_TRUE_MESSAGE(result, "書き込み成功時はtrueを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_program, "NAND Programが1回呼ばれること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected_pba, s_l2p_table[target_lba], "L2Pテーブルが新しいPBAへ更新されていること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_invalidate, "初回書き込みのため、Invalidate(旧PBAの破棄)は呼ばれないこと");
}

/* 4. 正常系: 既存領域への上書き(Overwrite)とガベージコレクション準備 */
void test_FTL_WriteAndMap_Overwrite_ShouldMapAndInvalidateOldPba(void) {
    uint8_t data[4096] = {0};
    uint32_t target_lba = 200U;
    
    /* 事前準備: すでにPBA 500 にマッピングされている状態を作る */
    s_l2p_table[target_lba] = 500U;
    uint32_t expected_new_pba = 1000U;
    
    bool result = FTL_WriteAndMap(target_lba, data);
    
    TEST_ASSERT_TRUE_MESSAGE(result, "上書き成功時はtrueを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected_new_pba, s_l2p_table[target_lba], "L2Pテーブルが新しいPBAへ更新されていること");
    
    /* ガベージコレクション用連携の検証 */
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_invalidate, "上書き時は、旧PBAのInvalidate関数が必ず呼ばれること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(500U, g_last_invalidated_pba, "Invalidate対象が正しく「上書き前の旧PBA」であること");
}

/* 5. 異常系(最重要): NAND書き込み失敗時のアトミック性証明と不良ブロック隔離 */
void test_FTL_WriteAndMap_ProgramFails_ShouldNotUpdateL2pAndMarkGbb(void) {
    uint8_t data[4096] = {0};
    uint32_t target_lba = 300U;
    
    /* 事前準備: すでにPBA 600 にマッピングされている状態 */
    s_l2p_table[target_lba] = 600U;
    
    /* NANDへのハードウェア書き込みが失敗(Program Fail)する最悪のシナリオ */
    g_mock_program_success = false;
    uint32_t expected_failed_pba = 1000U;
    
    bool result = FTL_WriteAndMap(target_lba, data);
    
    TEST_ASSERT_FALSE_MESSAGE(result, "NAND書き込み失敗時はfalseを返すこと(フェイルセーフ)");
    
    /* 【トランザクション保護の証明】 */
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(600U, s_l2p_table[target_lba], "【最重要】書き込み失敗時は絶対にL2Pを更新せず、古い正常なデータ(PBA 600)を死守すること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_invalidate, "エラー時は旧データのInvalidateを行わないこと(旧データを保護)");
    
    /* 【異常ブロックの隔離】 */
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_gbb, "書き込みに失敗したブロックは後発不良(GBB)登録関数が呼ばれること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected_failed_pba, g_last_gbb_pba, "GBB登録対象が、書き込みに失敗した新しいPBAであること");
}

0

查看点赞用户列表

0

注册新用户并更便捷地使用 Qiita

  1. 获取符合您需求的文章
  2. 高效回顾有用信息
  3. 支持深色主题

注册后可享受的功能

0

转到喜欢该文章的用户列表

0

删除文章

已删除的文章无法恢复。

本文的草稿也将被删除。

确定要删除此文章吗?

来源:Qiita · Unity 开发社区 · qiita.com