/* sdk/meow_cachegov.c — 磁盘缓存 page-cache 治理 SDK 库(A:DONTNEED 主导侧)。 * * 权威方案:devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md。归属(ARCHITECTURE_PRINCIPLES ③混合): * A 热路径(register/tick/drop_finished/refault)随 SDK 库;C(/proc/meminfo Cached 监控) * 归 meow_svc_perf,本库只读其共享状态文件 MEOW_CACHEGOV_C_FILE 作为降载信号。 * * 红线(务必保留本注释):**L2 禁止**——绝不丢 `[read_pos, ...)` 任何未解码区(含 read_pos 前方 * 刚写入尚未解码的页)。本库所有 drop 的上界只能是 read_pos-keep_hot(L1)或整文件曲终(L0)。 * * 分层:只依赖 pthread + meow_log + POSIX(posix_fadvise),不依赖 core/App 其它 TU。 * 自含:refault 滚窗 + 三级回退(放大 keep_hot → 降 L0 → 关 A)。 */ #include "include/meow_cachegov.h" #include #include #include #include #include #include #include /* meow_ui.h:SDK 公共伞头(含 meow_log/meow_ui_log.h + 公共类型)。 */ #include "meow_ui.h" /* 共享协议头:C 侧状态文件路径(meow_svc_perf 写入 / 各 App 读取,同源防漂移)。 */ #include "meow_ipc.h" #define MEOW_CACHEGOV_MAXREG 8 /* 同时治理的缓存文件上限(ncm 单点播放,远小于此) */ #define MEOW_CACHEGOV_REFAULTRING 64 /* refault 滚窗环形缓冲 */ #define MEOW_CACHEGOV_WINDOW_MS 10000 /* refault 滚窗 10s */ #define MEOW_CACHEGOV_HOTESCALATE 1 /* 回退档>=1 时 keep_hot 放大倍数(翻倍) */ #define MEOW_CACHEGOV_DROP_THROTTLE (64u * 1024u) /* L1 每次至少推进 64KB 才再 drop(省 open/fadvise) */ typedef struct cg_entry { char path[512]; uint64_t size; /* 标称大小(流式=nominal;命中=实际) */ uint64_t (*read_pos_fn)(void *ctx); void *ctx; uint64_t dropped_to; /* 已 DONTNEED 的已读前缀上界(只推进,不回退) */ int used; } cg_entry_t; static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; static struct meow_cachegov_cfg g_cfg = { .enabled = 1, .cached_threshold_kb = 0, /* C 归属 svc_perf,本库只读 */ .dirty_warn_kb = 0, /* 同上 */ .keep_hot_bytes = (256u * 1024u), /* 保 256KB 解码窗口供倒带 */ .min_hot_ahead = (0), /* L2 红线防御值;绝不参与 drop */ .drop_mode = 0, /* 默认仅曲终(L0);L1 需显式开(带 seek 回退) */ .refault_backoff = 3, /* 10s 内 3 次 refault → 回退 */ }; static cg_entry_t g_reg[MEOW_CACHEGOV_MAXREG]; static int g_backoff = 0; /* 0=正常 1=keep_hot放大 2=降L0 3=A关 */ static int64_t g_last_step_ms = -1000000000000LL; /* 上次升/降档时刻(初始很远 → 首次可动作) */ static int64_t g_refault_ring[MEOW_CACHEGOV_REFAULTRING]; static int g_refault_head = 0; static int g_refault_n = 0; /* ---- 时钟(宿主测试桩可注入) ---- */ static int64_t g_test_now_ms = -1; static int64_t now_ms(void) { if (g_test_now_ms >= 0) return g_test_now_ms; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } void meow_cachegov_test_set_time_ms(int64_t t) { g_test_now_ms = t; } /* ---- C 侧状态文件(svc_perf 写 / 本库读) ---- */ static const char *c_file(void) { const char *e = getenv("MEOW_CACHEGOV_C_FILE"); return (e && e[0]) ? e : MEOW_CACHEGOV_C_FILE; } /* C 状态(svc_perf 写)最近一次解析缓存:下载 cb 每 4KB 块会查一次,避免每块都读文件(200ms TTL)。 */ static int g_cached_c_dl = 0, g_cached_c_pf = 0; static int64_t g_cached_c_at = 0; /* 读 C 状态文件(缺省 0=不降载)。解析失败/文件不存在 → 全部 0。 */ static void read_c_state(int *dl_pause, int *pf_pause, long *cached_kb, long *dirty_kb) { if (dl_pause) *dl_pause = 0; if (pf_pause) *pf_pause = 0; if (cached_kb) *cached_kb = 0; if (dirty_kb) *dirty_kb = 0; FILE *f = fopen(c_file(), "r"); if (!f) return; char line[160]; while (fgets(line, sizeof line, f)) { if (!strncmp(line, "cached_kb=", 10)) { if (cached_kb) *cached_kb = atol(line + 10); } else if (!strncmp(line, "dirty_kb=", 9)) { if (dirty_kb) *dirty_kb = atol(line + 9); } else if (!strncmp(line, "dl_pause=", 9)) { if (dl_pause) *dl_pause = atol(line + 9) ? 1 : 0; } else if (!strncmp(line, "prefetch_pause=", 15)) { if (pf_pause) *pf_pause = atol(line + 15) ? 1 : 0; } } fclose(f); } /* ---- refault 滚窗 ---- */ void meow_cachegov_note_refault(void) { int64_t t = now_ms(); pthread_mutex_lock(&g_mu); g_refault_ring[g_refault_head] = t; g_refault_head = (g_refault_head + 1) % MEOW_CACHEGOV_REFAULTRING; if (g_refault_n < MEOW_CACHEGOV_REFAULTRING) g_refault_n++; pthread_mutex_unlock(&g_mu); } static int refaults_last_window(void) { int64_t now = now_ms(); int cnt = 0, start = (g_refault_head - g_refault_n + MEOW_CACHEGOV_REFAULTRING) % MEOW_CACHEGOV_REFAULTRING; for (int i = 0; i < g_refault_n; i++) { int64_t t = g_refault_ring[(start + i) % MEOW_CACHEGOV_REFAULTRING]; if (t >= now - MEOW_CACHEGOV_WINDOW_MS) cnt++; } return cnt; } /* 曲终/cur_finished 后:整文件 L0 DONTNEED。 */ int meow_cachegov_drop_finished(const char *path) { if (!path) return -1; pthread_mutex_lock(&g_mu); for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) { if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { g_reg[i].used = 0; break; } } int lbf = g_backoff, en = g_cfg.enabled; pthread_mutex_unlock(&g_mu); if (!en || lbf >= 3) { /* A 已整体关闭:连曲终 L0 也不丢(§2.3 步骤 3) */ meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished skip %s (A off, backoff=%d)", path, lbf); return 0; } int fd = open(path, O_RDONLY); if (fd < 0) return -1; /* len=0 → 到 EOF(整文件)。曲终播放器已不再读该文件,重播=冷缓存首读(等价未命中)。 */ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); close(fd); meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished L0 %s", path); return 0; } /* L1:丢已读前缀 [dropped_to, read_pos-keep_hot)。返回 1=有丢。 */ static int entry_drop_l1(cg_entry_t *e, size_t keep) { if (!e->read_pos_fn) return 0; uint64_t pos = e->read_pos_fn(e->ctx); if (pos <= keep) return 0; /* keep_hot 覆盖整个已读前缀,无需丢 */ uint64_t drop_end = pos - (uint64_t)keep; /* L1 上界:read_pos 前方绝不丢(L2 红线) */ if (drop_end <= e->dropped_to) return 0; /* 无推进(seek 回退 / 前进不足) */ if (drop_end - e->dropped_to < MEOW_CACHEGOV_DROP_THROTTLE && e->dropped_to != 0) return 0; /* 节流:不足 64KB 不重复 open */ int fd = open(e->path, O_RDONLY); if (fd < 0) return 0; off_t off = (off_t)e->dropped_to; off_t len = (off_t)(drop_end - e->dropped_to); ssize_t r = posix_fadvise(fd, off, len, POSIX_FADV_DONTNEED); close(fd); if (r != 0) return 0; e->dropped_to = drop_end; meow_log(MEOW_LOG_INFO, "[cachegov] L1 drop %s off=%lld len=%lld (pos=%lld keep=%zu)", e->path, (long long)off, (long long)len, (long long)pos, keep); return 1; } int meow_cachegov_tick(void) { pthread_mutex_lock(&g_mu); int64_t now = now_ms(); /* ① 回退评估(滚窗 10s):refault 超门限 → 升一档;滚窗干净 ≥10s → 降一档。 * 每升降一档需间隔 ≥10s(逐级回退,不因窗内持续 refault 一次跳过多档,防误回退)。 */ int cnt = refaults_last_window(); if (cnt >= g_cfg.refault_backoff && g_cfg.refault_backoff > 0 && g_backoff < 3 && (now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) { g_backoff = g_backoff + 1; g_last_step_ms = now; meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (refault=%d/%lds)", g_backoff, cnt, MEOW_CACHEGOV_WINDOW_MS / 1000); } else if (cnt == 0 && g_backoff > 0 && (now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) { g_backoff = g_backoff - 1; g_last_step_ms = now; meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (clean window)", g_backoff); } size_t keep = g_cfg.keep_hot_bytes; if (g_backoff >= MEOW_CACHEGOV_HOTESCALATE) keep = g_cfg.keep_hot_bytes * 2; /* 档1:放大 keep_hot */ int l1_on = g_cfg.enabled && g_cfg.drop_mode >= 1 && g_backoff < 2; /* 档2:L1 关 */ int en = g_cfg.enabled; /* ② L1 丢弃 */ int dropped = 0; if (en && l1_on) { for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) if (g_reg[i].used) dropped += entry_drop_l1(&g_reg[i], keep); } pthread_mutex_unlock(&g_mu); return dropped; } /* ---- 注册 / 注销 ---- */ int meow_cachegov_register(const char *path, uint64_t size, uint64_t (*read_pos_fn)(void *ctx), void *ctx) { if (!path) return -1; pthread_mutex_lock(&g_mu); int slot = -1; for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) { if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { slot = i; break; } if (!g_reg[i].used && slot < 0) slot = i; } if (slot < 0) { pthread_mutex_unlock(&g_mu); return -1; } snprintf(g_reg[slot].path, sizeof g_reg[slot].path, "%s", path); g_reg[slot].size = size; g_reg[slot].read_pos_fn = read_pos_fn; g_reg[slot].ctx = ctx; g_reg[slot].dropped_to = 0; g_reg[slot].used = 1; pthread_mutex_unlock(&g_mu); meow_log(MEOW_LOG_INFO, "[cachegov] register %s size=%llu", path, (unsigned long long)size); return 0; } int meow_cachegov_unregister(const char *path) { if (!path) return -1; pthread_mutex_lock(&g_mu); int rc = -1; for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { g_reg[i].used = 0; rc = 0; break; } pthread_mutex_unlock(&g_mu); return rc; } /* ---- 降载查询(A 回退 OR svc_perf C) ---- */ static void c_state(int *dl_pause, int *pf_pause) { int64_t now = now_ms(); pthread_mutex_lock(&g_mu); if (g_cached_c_at == 0 || (now - g_cached_c_at) > 200) { read_c_state(&g_cached_c_dl, &g_cached_c_pf, NULL, NULL); g_cached_c_at = now; } if (dl_pause) *dl_pause = g_cached_c_dl; if (pf_pause) *pf_pause = g_cached_c_pf; pthread_mutex_unlock(&g_mu); } int meow_cachegov_download_paused(void) { pthread_mutex_lock(&g_mu); int a = (g_backoff >= 2); pthread_mutex_unlock(&g_mu); int c = 0; c_state(&c, NULL); return (a || c) ? 1 : 0; } int meow_cachegov_prefetch_paused(void) { pthread_mutex_lock(&g_mu); int a = (g_backoff >= 3); pthread_mutex_unlock(&g_mu); int c = 0; c_state(NULL, &c); return (a || c) ? 1 : 0; } /* ---- 配置 ---- */ int meow_cachegov_set_cfg(const struct meow_cachegov_cfg *cfg) { if (!cfg) return -1; pthread_mutex_lock(&g_mu); g_cfg = *cfg; pthread_mutex_unlock(&g_mu); return 0; } int meow_cachegov_get_cfg(struct meow_cachegov_cfg *cfg) { if (!cfg) return -1; pthread_mutex_lock(&g_mu); *cfg = g_cfg; pthread_mutex_unlock(&g_mu); return 0; } int meow_cachegov_backoff_level(void) { pthread_mutex_lock(&g_mu); int v = g_backoff; pthread_mutex_unlock(&g_mu); return v; }