fix(storage): dhara 移植层 is_free 整页精度 + HAL 越界守卫;日志读写批量化
登记簿 N-21(报告 N-12/N-13)与 N-24(报告 N-16)两项未闭环问题修复并销项。 N-12:dhara_nand_is_free() 旧实现只读页首 64 B 判全 0xFF,会把"页首 0xFF、后段有数据"的已编程页误判空闲,dhara 遂往已编程页再编程 = 位碰撞、 journal/map 指向垃圾且不报任何错。改整页扫描(栈上 64 B 分块、页首命中即 早退,故"已编程且页首有数据"的绝大多数页成本与旧实现同价)。 N-13:报告原建议经核对不成立——本仓裁剪版 dhara 既无 DHARA_NO_PAGE(哨兵 实为 DHARA_PAGE_NONE,journal.h:46)也无 DHARA_E_UNSUPPORTED(error.h 枚举 里没有,照抄直接编译失败),且 map.c 三处 dhara_journal_copy 调用点各自已 挡掉哨兵。真风险是移植层零上界校验:越界页号被芯片地址位截断成某个真实页 → 静默读写错页。改为六个 HAL 入口统一几何上界守卫 + DHARA_E_CORRUPT_MAP。 错误码选择是刻意的:BAD_BLOCK 会让 journal.c:904 标坏无辜好块,RECOVER 会 让 map.c:275 启动回收;无错误通道的 is_free/is_bad/mark_bad 一律取保守值。 N-16 写侧:一次排空合并成"每文件一次 open/write/close"(≤8 条 = 512 B 静态缓冲)。掉电语义不变——批次仍在同一次 drain 调用内 close,而 close 回写目录项这个不同 LPN 会把 FTL 单页写回缓存逼出到 NAND;失败尾部回投 队列,条目不丢(旧实现 -2 卷忙会直接丢掉那一条)。容量轮转仍只在按条慢 路径做,批量路径发现装不下一批就返回 -3 退回。 N-16 读侧:s_read_raw() 加 8 条滑动读窗口 + 写者代数失效,API 与 UI 零改动。 验证:@build 0 错 0 警(Code 213,540 / ER_IROM1 232,280 B = 22.2%, RW_IRAM1 75,232 B = 57.4%,ZI +1,640 B 全为静态缓冲、不吃任务栈); @test PASS,且台架 journal 恢复点与修复前同轨——连续四次启动 sectors used 恒 14,094、Map head 每轮 +128 单调推进、epoch 稳定 3 → 整页 is_free 未 改变恢复语义、无双编程、无回退;stack_report 9 个任务全 PASS(defaultTask 静态最坏链 3,024 B / 余 40%,实测峰值 2,164 B / 余 57%)。 新增主机侧移植层单测 test/host/nand_port/(第 16 个 -unit-test 钩子): RAM 假芯片 + 真实 dhara map/journal 源,149 断言,含"旧算法只看页首 64 B" 对照用例(将来有人把 is_free 改回去会立刻红)与 dhara 级 resume 双编程计数 为 0 的场景。L0.5 由 15 runner / 955 断言 → 16 runner / 1,104 断言。 文档:存储架构说明 §5.3 加移植层两条硬约束;运行与自检记录设计说明 §4.4/§4.5 写批量化与读窗口;GD5F Trap Records 新增 Trap 07(含三条遗留 代价与一条待办编译期断言);登记簿 N-21/N-24 删行销项并同步 README §1/§2.0/§2.1、排期详述 C3、报告 §0.1/§0.2/§0.4/§4.x/§7 与去处表; 钩子计数 33→34、主机侧单测 15→16 全库同步。
This commit is contained in:
+299
-62
@@ -45,6 +45,12 @@ static const uint32_t s_caps[SYSLOG_COUNT] = { SYSLOG_RUN_MAX, SYSLOG_SELFTEST_M
|
||||
|
||||
#define SYSLOG_QUEUE_MAX 8u
|
||||
|
||||
/* N-24(2026-10-15,报告 N-16 写侧):一次排空的合并写入上限。
|
||||
* 8 条 × 64 B = 512 B 静态缓冲(不吃任务栈),与队列深度 SYSLOG_QUEUE_MAX 同值
|
||||
* ——即"一轮排空最多一次 open/write/close"。 */
|
||||
#define SYSLOG_BATCH_MAX 8u
|
||||
static uint8_t s_batch[SYSLOG_BATCH_MAX * SYSLOG_REC_SIZE];
|
||||
|
||||
/* 待发条目:desc 为调用方静态串(Flash 字面量),只存指针不拷贝 */
|
||||
typedef struct {
|
||||
uint8_t id;
|
||||
@@ -52,32 +58,81 @@ typedef struct {
|
||||
} syslog_entry_t;
|
||||
|
||||
static syslog_entry_t s_preboot[SYSLOG_QUEUE_MAX]; /* 启动前待发数组(RTOS 前单生产者/后单消费者,免锁) */
|
||||
static syslog_entry_t s_batch_src[SYSLOG_BATCH_MAX]; /* 本批条目的原始来源(N-24:未落盘的尾部要回投队列,故必须留住 id + desc 指针);
|
||||
* 唯一写者 defaultTask,走静态不吃栈 */
|
||||
static volatile uint8_t s_preboot_cnt; /* 数组条目数 */
|
||||
static osMessageQueueId_t s_queue; /* 运行期消息队列(syslog_drain 首次调用时惰性创建) */
|
||||
static const osMessageQueueAttr_t s_queue_attr = { .name = "syslog" };
|
||||
static volatile uint8_t s_fs_ready; /* 卷挂载 + /log 目录就绪后置位 */
|
||||
static volatile uint8_t s_mount_fail_reported; /* P2-9:建目录失败日志去重(成功复位) */
|
||||
|
||||
/* N-24(2026-10-15,报告 N-16 读侧):8 条滑动读窗口 + 写者代数。
|
||||
* 窗口 512 B 走静态(不吃任务栈;defaultTask/uiTask 最坏链见 stack_report.py,
|
||||
* 预算余量见 docs/内存管理/RAM预算台账.md)。s_rd_gen 由写者自增、读者比对,
|
||||
* 对齐 32 位在 ARM 上原子,跨任务无需临界区。 */
|
||||
#define SYSLOG_RD_WINDOW 8u
|
||||
static uint8_t s_rd_win[SYSLOG_RD_WINDOW * SYSLOG_REC_SIZE];
|
||||
static int32_t s_rd_win_id = -1; /* -1 = 窗口空 */
|
||||
static uint32_t s_rd_win_base; /* 窗口首条序号 */
|
||||
static uint32_t s_rd_win_cnt; /* 窗口内有效条数 */
|
||||
static uint32_t s_rd_win_gen; /* 填充时看到的写者代数 */
|
||||
static volatile uint32_t s_rd_gen[SYSLOG_COUNT]; /* 每次成功落盘 / unlink 后自增 */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:追加一条记录(格式化 64B + FA_OPEN_APPEND;容量满先删除重建)
|
||||
* 入口参数:id - 日志 ID syslog_id_t
|
||||
* desc - 描述串(UTF-8,超长截断至 SYSLOG_DESC_MAX) const char *
|
||||
* 返回值:0 - 成功;-1 - 写失败;-2 - 卷被占用(顺延,非故障) int
|
||||
* 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载
|
||||
* 函数功能:格式化一条 64 B 定长记录(时间戳 + 空格 + epoch + 空格 + 描述)
|
||||
* 入口参数:id - 日志 ID(当前不参与编码,仅为接口对称) syslog_id_t
|
||||
* desc - 描述串(UTF-8,超长截断至 SYSLOG_DESC_MAX) const char *
|
||||
* p_rec - 输出缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) uint8_t *
|
||||
* 返回值:无
|
||||
* 限定条件:时间戳未就绪时落 "1970-01-01 08:00:00" 占位串
|
||||
* 函数说明:N-24(2026-10-15,报告 N-16)从 s_append 抽出,供"单条写"与
|
||||
* "批量写"共用——两条路径必须产出逐字节相同的记录,否则 UI 按定长
|
||||
* 字段偏移解析会错位。
|
||||
*/
|
||||
static int s_append(syslog_id_t id, const char *desc) {
|
||||
static FIL f; /* static:FF_FS_TINY=0 → 局部 FIL 吃 528 B 栈;
|
||||
* 本函数全程在 vol_lock 区间内,跨任务被卷锁串行化 */
|
||||
FRESULT fr;
|
||||
UINT bw = 0u;
|
||||
uint8_t rec[SYSLOG_REC_SIZE];
|
||||
static void s_format_rec(syslog_id_t id, const char *desc, uint8_t *p_rec) {
|
||||
char ts[SYSLOG_TS_LEN + 1u];
|
||||
uint32_t epoch;
|
||||
size_t dl;
|
||||
|
||||
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT || desc == NULL) {
|
||||
(void)id;
|
||||
memset(p_rec, 0u, SYSLOG_REC_SIZE);
|
||||
if (sys_clock_get_str(ts, sizeof(ts)) == NULL) {
|
||||
memcpy(ts, "1970-01-01 08:00:00", SYSLOG_TS_LEN);
|
||||
}
|
||||
ts[SYSLOG_TS_LEN] = '\0';
|
||||
epoch = sys_clock_get();
|
||||
if (epoch == UINT32_MAX) {
|
||||
epoch = 0u;
|
||||
}
|
||||
memcpy(p_rec, ts, SYSLOG_TS_LEN);
|
||||
p_rec[SYSLOG_TS_LEN] = ' ';
|
||||
(void)snprintf((char *)(p_rec + SYSLOG_EPOCH_OFFS), SYSLOG_EPOCH_LEN + 1u, "%010u", (unsigned int)epoch);
|
||||
p_rec[SYSLOG_DESC_OFFS - 1u] = ' ';
|
||||
dl = strlen(desc);
|
||||
if (dl > SYSLOG_DESC_MAX) {
|
||||
dl = SYSLOG_DESC_MAX;
|
||||
}
|
||||
memcpy(p_rec + SYSLOG_DESC_OFFS, desc, dl);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:把一条**已格式化**的 64 B 记录追加落盘(容量满先删除重建)
|
||||
* 入口参数:id - 日志 ID syslog_id_t
|
||||
* p_rec - 记录缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) const uint8_t *
|
||||
* 返回值:0 - 成功;-1 - 写失败;-2 - 卷被占用(顺延,非故障) int
|
||||
* 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载
|
||||
*/
|
||||
static int s_append_rec(syslog_id_t id, const uint8_t *p_rec) {
|
||||
static FIL f; /* static:FF_FS_TINY=0 → 局部 FIL 吃 528 B 栈;
|
||||
* 本函数全程在 vol_lock 区间内,跨任务被卷锁串行化 */
|
||||
static uint8_t s_fault_reported[SYSLOG_COUNT];
|
||||
FRESULT fr;
|
||||
UINT bw = 0u;
|
||||
int rc = 0;
|
||||
|
||||
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT || p_rec == NULL) {
|
||||
return -1;
|
||||
}
|
||||
/* 卷锁 try:本任务 = 看门狗喂狗点,绝不允许阻塞等锁(阻塞 = 喂狗停摆 =
|
||||
@@ -94,33 +149,11 @@ static int s_append(syslog_id_t id, const char *desc) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
memset(rec, 0u, sizeof(rec));
|
||||
if (sys_clock_get_str(ts, sizeof(ts)) == NULL) {
|
||||
memcpy(ts, "1970-01-01 08:00:00", SYSLOG_TS_LEN);
|
||||
}
|
||||
ts[SYSLOG_TS_LEN] = '\0';
|
||||
epoch = sys_clock_get();
|
||||
if (epoch == UINT32_MAX) {
|
||||
epoch = 0u;
|
||||
}
|
||||
memcpy(rec, ts, SYSLOG_TS_LEN);
|
||||
rec[SYSLOG_TS_LEN] = ' ';
|
||||
(void)snprintf((char *)(rec + SYSLOG_EPOCH_OFFS), SYSLOG_EPOCH_LEN + 1u, "%010u", (unsigned int)epoch);
|
||||
rec[SYSLOG_DESC_OFFS - 1u] = ' ';
|
||||
dl = strlen(desc);
|
||||
if (dl > SYSLOG_DESC_MAX) {
|
||||
dl = SYSLOG_DESC_MAX;
|
||||
}
|
||||
memcpy(rec + SYSLOG_DESC_OFFS, desc, dl);
|
||||
|
||||
/* L1 文件级自愈:陈旧/损坏条目 → 丢弃重建、重试一次(与上方容量满重建
|
||||
* 同一机制);L2 错误去重:持续故障只报第一次,恢复前静默(消除"每次
|
||||
* 开机同一错误刷屏")。打开模式必须带 FA_WRITE 位:本 FatFs 移植的
|
||||
* FA_OPEN_APPEND(0x30) 不含 FA_WRITE(0x02),不带写位打开的文件 f_write
|
||||
* 恒返回 FR_DENIED(7)——2026-09-20 台架实测 /log 写失败 ×5/开机 真根因 */
|
||||
static uint8_t s_fault_reported[SYSLOG_COUNT];
|
||||
int rc = 0;
|
||||
|
||||
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr != FR_OK && (fr == FR_DENIED || fr == FR_INVALID_OBJECT ||
|
||||
fr == FR_INT_ERR || fr == FR_DISK_ERR ||
|
||||
@@ -130,14 +163,14 @@ static int s_append(syslog_id_t id, const char *desc) {
|
||||
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
|
||||
}
|
||||
if (fr == FR_OK) {
|
||||
fr = f_write(&f, rec, SYSLOG_REC_SIZE, &bw);
|
||||
fr = f_write(&f, p_rec, SYSLOG_REC_SIZE, &bw);
|
||||
(void)f_close(&f);
|
||||
if (fr != FR_OK || bw != SYSLOG_REC_SIZE) {
|
||||
(void)f_unlink(s_paths[id]);
|
||||
bw = 0u;
|
||||
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr == FR_OK) {
|
||||
fr = f_write(&f, rec, SYSLOG_REC_SIZE, &bw);
|
||||
fr = f_write(&f, p_rec, SYSLOG_REC_SIZE, &bw);
|
||||
(void)f_close(&f);
|
||||
}
|
||||
if (fr != FR_OK || bw != SYSLOG_REC_SIZE) {
|
||||
@@ -153,6 +186,106 @@ static int s_append(syslog_id_t id, const char *desc) {
|
||||
s_fault_reported[id] = 1u;
|
||||
DBG_ERROR("write %s fail (%d)", s_paths[id], (int)fr);
|
||||
}
|
||||
/* 读窗口失效:本函数可能改过文件内容(追加 / 容量满重建 / 自愈 unlink),
|
||||
* 一律自增代数让读者重填——失败路径多失效一次只是多读一轮,不会读错。 */
|
||||
s_rd_gen[id]++;
|
||||
vol_lock_release();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:追加一条记录(格式化 + 单条落盘)
|
||||
* 入口参数:id - 日志 ID syslog_id_t
|
||||
* desc - 描述串(UTF-8,超长截断至 SYSLOG_DESC_MAX) const char *
|
||||
* 返回值:0 - 成功;-1 - 写失败;-2 - 卷被占用(顺延,非故障) int
|
||||
* 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载
|
||||
* 函数说明:慢路径 / 兼容入口。批量排空走 s_append_many();容量轮转、
|
||||
* sys_log_evidence() 的 flush 诱导、开机首条"power on/reboot"仍走这里。
|
||||
*/
|
||||
static int s_append(syslog_id_t id, const char *desc) {
|
||||
uint8_t rec[SYSLOG_REC_SIZE];
|
||||
|
||||
if (desc == NULL) {
|
||||
return -1;
|
||||
}
|
||||
s_format_rec(id, desc, rec);
|
||||
return s_append_rec(id, rec);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:把 n 条**已格式化**的记录一次性追加到同一日志文件(一次 open/write/close)
|
||||
* 入口参数:id - 日志 ID syslog_id_t
|
||||
* p_buf - 连续 n 条 64 B 记录(n × SYSLOG_REC_SIZE 字节) const uint8_t *
|
||||
* n - 记录条数(1 ~ SYSLOG_BATCH_MAX) uint32_t
|
||||
* 返回值:0 - 成功;-1 - 写失败;-2 - 卷被占用(顺延,非故障);
|
||||
* -3 - 剩余容量不足 n 条,本批不能走快路径(交回按条慢路径做轮转) int
|
||||
* 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载
|
||||
* 函数说明:N-24(2026-10-15,报告 N-16 写侧)。旧实现每条记录一轮
|
||||
* f_open + f_write(64 B) + f_close,而 f_close 要回写目录项、f_write
|
||||
* 要对目标扇区做读-改-写 → 每条日志 2~4 次 NAND 页读写。本函数把
|
||||
* 同一次排空里属于同一文件的最多 8 条合并成一次 512 B 追加写。
|
||||
* **不改掉电语义**:批次仍在同一次 syslog_drain() 调用内写完并
|
||||
* f_close;close 回写目录项(另一个 LPN)会把 FTL 的单页写回缓存逼出
|
||||
* 到 NAND——该"写到不同 LPN 才刷前一脏页"的语义见 sys_log_evidence()
|
||||
* 的 flush 诱导注释。落盘时机与旧实现每条 close 等价,批次不跨调用驻留 RAM。
|
||||
* 自愈重试与错误去重的判据与 s_append_rec 一致;容量不足时返回 -3
|
||||
* 而不是就地 unlink——"满了就重建"必须由按条慢路径统一执行,
|
||||
* 否则一批 8 条会跨越多轮重建、语义说不清。
|
||||
*/
|
||||
static int s_append_many(syslog_id_t id, const uint8_t *p_buf, uint32_t n) {
|
||||
static FIL f;
|
||||
static uint8_t s_batch_fault[SYSLOG_COUNT];
|
||||
FRESULT fr;
|
||||
UINT bw = 0u;
|
||||
UINT total;
|
||||
int rc = 0;
|
||||
|
||||
if (((uint32_t)id >= (uint32_t)SYSLOG_COUNT) || (p_buf == NULL) ||
|
||||
(n == 0u) || (n > SYSLOG_BATCH_MAX)) {
|
||||
return -1;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return -2;
|
||||
}
|
||||
if ((syslog_count(id) + n) > s_caps[id]) {
|
||||
vol_lock_release();
|
||||
return -3;
|
||||
}
|
||||
total = (UINT)(n * SYSLOG_REC_SIZE);
|
||||
|
||||
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr != FR_OK && (fr == FR_DENIED || fr == FR_INVALID_OBJECT ||
|
||||
fr == FR_INT_ERR || fr == FR_DISK_ERR ||
|
||||
fr == FR_NO_FILE || fr == FR_NO_PATH ||
|
||||
fr == FR_INVALID_NAME || fr == FR_NO_FILESYSTEM)) {
|
||||
(void)f_unlink(s_paths[id]);
|
||||
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
|
||||
}
|
||||
if (fr == FR_OK) {
|
||||
fr = f_write(&f, p_buf, total, &bw);
|
||||
(void)f_close(&f);
|
||||
if (fr != FR_OK || bw != total) {
|
||||
(void)f_unlink(s_paths[id]);
|
||||
bw = 0u;
|
||||
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr == FR_OK) {
|
||||
fr = f_write(&f, p_buf, total, &bw);
|
||||
(void)f_close(&f);
|
||||
}
|
||||
if (fr != FR_OK || bw != total) {
|
||||
rc = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
if (rc == 0) {
|
||||
s_batch_fault[id] = 0u;
|
||||
} else if (s_batch_fault[id] == 0u) {
|
||||
s_batch_fault[id] = 1u;
|
||||
DBG_ERROR("write %s x%lu fail (%d)", s_paths[id], (unsigned long)n, (int)fr);
|
||||
}
|
||||
s_rd_gen[id]++;
|
||||
vol_lock_release();
|
||||
return rc;
|
||||
}
|
||||
@@ -168,33 +301,78 @@ static int s_append(syslog_id_t id, const char *desc) {
|
||||
* open/read 可交错,读到的可能是半删除态文件。try 语义(timeout=0):
|
||||
* 本路径可由 uiTask 调用,uiTask 在 required_mask 内、须每 100ms 喂
|
||||
* 心跳,绝不能阻塞等锁;busy = 本次读失败(UI 下轮刷新重读,非故障)。
|
||||
*
|
||||
* N-24(2026-10-15,报告 N-16 读侧):加**8 条滑动窗口**。旧实现每读
|
||||
* 一条记录都要 open + lseek + read + close 一轮,UI 翻 20 条记录 =
|
||||
* 20 次目录项/ FAT 往返(报告 N-16 的"UI 翻记录逐条 open/seek/close")。
|
||||
* 记录定长 64 B,故一次 open + 一次 lseek + 一次 f_read 就能取回连续
|
||||
* 8 条(512 B),命中窗口时零 I/O 直接从 RAM 返回。API 与调用方
|
||||
* (ui_page_runlog / ui_page_selftest 的逐行取数)一律不变。
|
||||
* 失效机制:写者每次成功落盘/重建后把 s_rd_gen[id] 自增,读者比对
|
||||
* 填充时记录的代数——不匹配就重填。ARM 上对齐的 32 位读写是原子的,
|
||||
* 跨任务(uiTask 填、defaultTask 增)无需额外临界区;最坏情况是
|
||||
* 多读一轮,不会读到错数据。
|
||||
*/
|
||||
static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
|
||||
static FIL f; /* static:省 528 B 栈;open~close 全在卷锁区间内
|
||||
* (uiTask 与写者任务由 vol_lock 串行化,见上方 P2-10 说明) */
|
||||
FRESULT fr;
|
||||
UINT br = 0u;
|
||||
int rc;
|
||||
uint32_t total;
|
||||
uint32_t base;
|
||||
uint32_t want;
|
||||
|
||||
if (p_rec == NULL || idx >= syslog_count(id)) {
|
||||
if (p_rec == NULL) {
|
||||
return -1;
|
||||
}
|
||||
total = syslog_count(id);
|
||||
if (idx >= total) {
|
||||
return -1;
|
||||
}
|
||||
/* 快路径:窗口命中(同 id、代数未变、落在窗口区间内)→ 零 I/O */
|
||||
if ((s_rd_win_id == (int32_t)id) && (s_rd_win_gen == s_rd_gen[id]) &&
|
||||
(idx >= s_rd_win_base) && (idx < s_rd_win_base + s_rd_win_cnt)) {
|
||||
(void)memcpy(p_rec, s_rd_win + ((size_t)(idx - s_rd_win_base) * SYSLOG_REC_SIZE),
|
||||
SYSLOG_REC_SIZE);
|
||||
return 0;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return -1; /* 卷被占用:本次读失败(非故障,下轮重读) */
|
||||
}
|
||||
/* 重新校验:等锁期间写者可能已推进(虽然 try 语义下不会阻塞等待) */
|
||||
total = syslog_count(id);
|
||||
if (idx >= total) {
|
||||
vol_lock_release();
|
||||
return -1;
|
||||
}
|
||||
base = (idx / SYSLOG_RD_WINDOW) * SYSLOG_RD_WINDOW; /* 窗口按 8 条对齐 */
|
||||
want = total - base;
|
||||
if (want > SYSLOG_RD_WINDOW) {
|
||||
want = SYSLOG_RD_WINDOW;
|
||||
}
|
||||
fr = f_open(&f, s_paths[id], FA_READ);
|
||||
if (fr != FR_OK) {
|
||||
vol_lock_release();
|
||||
return -1;
|
||||
}
|
||||
fr = f_lseek(&f, (FSIZE_t)idx * SYSLOG_REC_SIZE);
|
||||
fr = f_lseek(&f, (FSIZE_t)base * SYSLOG_REC_SIZE);
|
||||
if (fr == FR_OK) {
|
||||
fr = f_read(&f, p_rec, SYSLOG_REC_SIZE, &br);
|
||||
fr = f_read(&f, s_rd_win, (UINT)(want * SYSLOG_REC_SIZE), &br);
|
||||
}
|
||||
(void)f_close(&f);
|
||||
rc = (fr == FR_OK && br == SYSLOG_REC_SIZE) ? 0 : -1;
|
||||
vol_lock_release();
|
||||
return rc;
|
||||
|
||||
if ((fr != FR_OK) || (br != (UINT)(want * SYSLOG_REC_SIZE))) {
|
||||
s_rd_win_cnt = 0u; /* 填充失败:作废窗口,避免半窗被当有效命中 */
|
||||
s_rd_win_id = -1;
|
||||
return -1;
|
||||
}
|
||||
s_rd_win_base = base;
|
||||
s_rd_win_cnt = want;
|
||||
s_rd_win_id = (int32_t)id;
|
||||
s_rd_win_gen = s_rd_gen[id];
|
||||
(void)memcpy(p_rec, s_rd_win + ((size_t)(idx - base) * SYSLOG_REC_SIZE), SYSLOG_REC_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -271,8 +449,6 @@ void syslog_mcu_mounted(void) {
|
||||
* 限定条件:仅 defaultTask(唯一写者)调用;卷未就绪时直接返回(条目滞留待重试)
|
||||
*/
|
||||
void syslog_drain(void) {
|
||||
syslog_entry_t e;
|
||||
|
||||
/* 运行期惰性创建消息队列:本函数仅由 defaultTask(调度器运行中)调用,
|
||||
保证内核对象创建发生在 osKernelStart 之后 */
|
||||
if (s_queue == NULL) {
|
||||
@@ -284,29 +460,90 @@ void syslog_drain(void) {
|
||||
if (!s_fs_ready || s_queue == NULL) {
|
||||
return;
|
||||
}
|
||||
while (s_preboot_cnt > 0u) {
|
||||
e = s_preboot[0];
|
||||
if (s_append((syslog_id_t)e.id, e.desc) != 0) {
|
||||
break; /* 落盘失败:保留剩余条目,下轮重试 */
|
||||
}
|
||||
memmove(&s_preboot[0], &s_preboot[1], sizeof(syslog_entry_t) * (size_t)(s_preboot_cnt - 1u));
|
||||
s_preboot_cnt--;
|
||||
}
|
||||
|
||||
/* N-24(2026-10-15,报告 N-16 写侧):一批一批地排空——同一文件、最多
|
||||
* SYSLOG_BATCH_MAX 条合并成一次 open/write/close(旧实现每条一轮
|
||||
* open/write/close,每条日志 2~4 次 NAND 页读写)。
|
||||
* 取数规则:preboot 数组按下标"看而不删",整批落盘成功才真正删除;
|
||||
* 队列必须取出才能看下一条,故未落盘的尾部一律回投队列——
|
||||
* **条目不丢**(旧实现 -2 卷忙会直接丢掉那一条)。 */
|
||||
for (;;) {
|
||||
if (osMessageQueueGet(s_queue, &e, 0u, 0u) != osOK) {
|
||||
break; /* 队列空 */
|
||||
syslog_entry_t e;
|
||||
uint32_t n = 0u;
|
||||
uint32_t n_pre = 0u;
|
||||
uint32_t i;
|
||||
uint32_t done;
|
||||
int rc;
|
||||
syslog_id_t bid = SYSLOG_RUN;
|
||||
|
||||
while (n < SYSLOG_BATCH_MAX) {
|
||||
uint8_t from_pre = 0u;
|
||||
|
||||
if (n_pre < s_preboot_cnt) {
|
||||
e = s_preboot[n_pre];
|
||||
n_pre++;
|
||||
from_pre = 1u;
|
||||
} else if (osMessageQueueGet(s_queue, &e, 0u, 0u) != osOK) {
|
||||
break; /* 队列空 */
|
||||
}
|
||||
if (n == 0u) {
|
||||
bid = (syslog_id_t)e.id;
|
||||
} else if ((syslog_id_t)e.id != bid) {
|
||||
/* 换文件:本批先落盘,这条留给下一批(原处不动或回投队列) */
|
||||
if (from_pre != 0u) {
|
||||
n_pre--;
|
||||
} else {
|
||||
(void)osMessageQueuePut(s_queue, &e, 0u, 0u);
|
||||
}
|
||||
break;
|
||||
}
|
||||
s_batch_src[n] = e;
|
||||
s_format_rec(bid, e.desc, s_batch + ((size_t)n * SYSLOG_REC_SIZE));
|
||||
n++;
|
||||
}
|
||||
{
|
||||
int arc = s_append((syslog_id_t)e.id, e.desc);
|
||||
if (arc == -2) {
|
||||
break; /* 卷被占用(FTP 文件操作进行中):本轮顺延,静默
|
||||
(条目已出队;run.log 尽力而为语义,不计故障) */
|
||||
if (n == 0u) {
|
||||
break; /* 无更多条目 */
|
||||
}
|
||||
|
||||
rc = s_append_many(bid, s_batch, n);
|
||||
done = n;
|
||||
if (rc == -3) {
|
||||
/* 剩余容量装不下一批:退回按条慢路径("满了 unlink 重建"只在
|
||||
* 那里做,避免一批跨越多轮重建、语义说不清) */
|
||||
done = 0u;
|
||||
for (i = 0u; i < n; i++) {
|
||||
int r1 = s_append(bid, s_batch_src[i].desc);
|
||||
|
||||
if (r1 == -2) {
|
||||
break; /* 卷忙:尾部回投队列 */
|
||||
}
|
||||
done++;
|
||||
if (r1 != 0) {
|
||||
DBG_ERROR("drop entry id=%d", (int)bid);
|
||||
break; /* 写失败:该条已丢,尾部回投 */
|
||||
}
|
||||
}
|
||||
if (arc != 0) {
|
||||
DBG_ERROR("drop entry id=%d", (int)e.id);
|
||||
break; /* 落盘失败:停止排空,避免连续丢弃 */
|
||||
} else if (rc == -1) {
|
||||
done = 0u;
|
||||
DBG_ERROR("drop %lu entries id=%d", (unsigned long)n, (int)bid);
|
||||
}
|
||||
/* rc == -2(卷忙):整批回投队列,静默顺延,不计故障 */
|
||||
|
||||
for (i = done; i < n; i++) {
|
||||
if (osMessageQueuePut(s_queue, &s_batch_src[i], 0u, 0u) != osOK) {
|
||||
DBG_ERROR("hold-over drop (queue full): id=%d", (int)s_batch_src[i].id);
|
||||
}
|
||||
}
|
||||
/* 本批消耗的 preboot 条目:已落盘的删除;未落盘的已转投队列,同样删除
|
||||
* (否则会重复落盘) */
|
||||
if (n_pre > 0u) {
|
||||
memmove(&s_preboot[0], &s_preboot[n_pre],
|
||||
sizeof(syslog_entry_t) * (size_t)(s_preboot_cnt - n_pre));
|
||||
s_preboot_cnt = (uint8_t)(s_preboot_cnt - n_pre);
|
||||
}
|
||||
if (rc != 0) {
|
||||
break; /* 本轮到此为止,下轮再试 */
|
||||
}
|
||||
}
|
||||
/* B9:SOE 落盘挂本函数尾部——同一唯一写者(defaultTask)、同一卷就绪门
|
||||
(上方 s_fs_ready 早退蕴含 /log 已建)、同一卷锁 try 语义;protectTask
|
||||
|
||||
Reference in New Issue
Block a user