/* meow_bgplay.h — MeowOS M4:后台播放保活 + 无头后台服务运行时表(FRAMEWORK_DESIGN §9)。 * * 定位: * ① 后台播放保活——退出播放中(声明 background_audio=true 且媒体会话 PLAYING)的 App * 不 SIGTERM:进程保留(音频经 audiod socket 天然续播),UI 冻结走 §10.5 的 * /tmp/meow_notif_active SDK 自抑机制(present 画进 back_buf 不上屏 + 输入排空)。 * **不做字面 SIGSTOP**:SIGSTOP 会冻结同进程播放线程、断供 PCM(§10.5 真机实证的坑)。 * 点图标/媒体卡片回前台=take 恢复原进程;停播后再退出=真退(走原 SIGTERM 序列)。 * ② 无头后台服务——MSG_REGISTER 注册 app.json `service.` 声明的服务:launcher * spawn 子进程(无 UI,stdio→/dev/null),死亡按 1/2/4/10s 退避重启,超 max_restart * 熔断(清该 App 通知 + 经注册连接广播 MSG_SYS{SVC_DEAD}),重注册可复位熔断。 * * 边界: * - 卡拔出例外:二进制在 TF 卡上的后台 App/服务仍 SIGTERM(防 SIGBUS),不适用保活。 * - 崩溃照收:waitpid WNOHANG 回收,异常退出经 on_bg_dead 回调走 crash 上报。 * - 关机前回收:meow_bgplay_kill_all() 有界 SIGTERM→SIGKILL→reap。 * * 自包含:不依赖 launcher static 符号;日志/时钟/卡检测/通知清理/发帧全经 * meow_bgplay_ops 注入(launcher 用 logmsg/sd_device_present/ipc_send;宿主单测用假件)。 * 单一写者=launcher 主循环(meow_ipc_process 排空点挂 tick),无锁。可独立单测(test_bgplay.c)。 */ #ifndef MEOW_BGPLAY_H #define MEOW_BGPLAY_H #include #include #include #include "meow_ipc.h" /* 后台保活 App 上限(同时驻留后台的播放中 App) */ #define MEOW_BG_MAX 4 /* 无头后台服务运行时槽上限(与 meow_acct.svcs 的 MEOW_MAX_SVCS 对齐) */ #define MEOW_SVC_RT_MAX 8 /* 退避重启档位(ms):1s/2s/4s/10s 封顶 */ #define MEOW_SVC_BACKOFF_1 1000u #define MEOW_SVC_BACKOFF_2 2000u #define MEOW_SVC_BACKOFF_4 4000u #define MEOW_SVC_BACKOFF_10 10000u /* 后台保活 App 槽。out_fd=其 stdout 管道读端(保活期继续 drain,防 App 写满管道阻塞)。 */ typedef struct meow_bg_entry { int used; char app_id[MEOW_APP_ID_MAX]; pid_t pid; int out_fd; int on_card; /* 二进制在 TF 卡上(卡拔需 SIGTERM 防 SIGBUS) */ uint64_t term_at_ms; /* 卡拔 SIGTERM 发出时刻(0=未发;SIGKILL 兜底计时) */ } meow_bg_entry_t; /* 无头后台服务运行时槽(静态声明在 meow_acct.svcs,此处为运行态)。 */ typedef struct meow_svc_rt { int used; char app_id[MEOW_APP_ID_MAX]; char svc[16]; char exec[129]; pid_t pid; /* >0 在跑;0=等待退避重启/已熔断 */ int reg_fd; /* 注册方 IPC 连接 fd(熔断广播投递用;-1=静态自动拉起) */ int restart; /* 1=死亡后退避重启 */ int max_restart; /* 熔断阈值(窗口内重启次数上限;0=不熔断) */ int window_ms; /* 重启窗口(0=累计不滑窗) */ int restart_count; /* 当前窗口内已重启次数 */ int circuit; /* 1=已熔断(清通知+广播过,不再拉起;重注册复位) */ int stopping; /* 1=主动停止/卡拔终止中(SIGTERM 已发,不重启) */ int on_card; /* exec 在 TF 卡上 */ uint64_t window_start_ms; /* 滑窗起点 */ uint64_t next_retry_ms; /* 退避下次拉起时刻(0=无排程) */ uint64_t term_at_ms; /* SIGTERM 发出时刻(0=未发;500ms 未死 SIGKILL) */ } meow_svc_rt_t; /* 依赖注入。全 NULL 可用(log 静默、now_ms 内部 CLOCK_MONOTONIC、card_present 恒 1、 * 通知清理/发帧跳过)。 * on_bg_dead:后台保活 App 进程退出(status=waitpid 原始状态;正常/异常由回调方判); * on_svc_dead:服务槽最终消亡(stopping 走完/不重启死亡;熔断不算消亡,槽保留待复位); * notif_clear_app:熔断时清该 App 全部通知,返回清除条数(<0=失败); * send_frame:熔断广播 MSG_SYS(launcher 侧须先查连接存活再发); * log_dir:svc 子进程 MEOW_LOG_DIR 注入(NULL 不注入)。 */ typedef struct meow_bgplay_ops { void (*log)(const char *fmt, ...); uint64_t (*now_ms)(void); int (*card_present)(void); void (*on_bg_dead)(const char *app_id, pid_t pid, int status); void (*on_svc_dead)(const char *app_id, const char *svc); int (*notif_clear_app)(const char *app_id); size_t (*send_frame)(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len); const char *log_dir; } meow_bgplay_ops; /* 注入 ops(拷贝);NULL=全清空复位(含两张表)。launcher 在 meow_ipc_init 末尾调一次。 */ void meow_bgplay_init(const meow_bgplay_ops *ops); /* ---- 后台播放保活判定(纯函数,无副作用)---- */ /* 退出时是否转后台保活:声明 background_audio 且该 App 媒体会话正在播放。 */ int meow_bg_should_keep(int bg_audio_declared, int media_playing); /* ---- 后台保活 App 表 ---- */ /* 转后台:登记 app_id/pid/out_fd。返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满。 */ int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card); /* 回前台:按 app_id 摘除槽(进程归还前台会话)。成功返回 0 且 *out_fd(可 NULL)带回 * 管道读端;未在后台返回 -1。 */ int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid); /* 该 App 是否在后台保活。 */ int meow_bg_is_backgrounded(const char *app_id); /* 当前后台保活数。 */ int meow_bg_count(void); /* 取第 i 槽(0..MEOW_BG_MAX-1)只读指针;空槽/越界返回 NULL。供 drain out_fd。 */ const meow_bg_entry_t *meow_bg_at(int i); /* 表变化计数(add/take/回收 递增);菜单 1s tick 比对刷新「·后台」标注用。 */ uint32_t meow_bgplay_change_seq(void); /* 周期调(主循环 ~10ms 排空点):waitpid WNOHANG 回收死亡后台 App(→on_bg_dead 清槽); * 卡拔例外:on_card 槽在卡不在时 SIGTERM,500ms 未死 SIGKILL。 */ void meow_bg_tick(void); /* ---- 无头后台服务 ---- */ /* 注册/拉起服务。exec 须可执行(先 access(X_OK),拒绝返回 MEOW_ERR_NOENT 不污染槽)。 * 返回 pid(>0)=已拉起;MEOW_ERR_EXISTS=同名服务在跑;MEOW_ERR_NOENT=exec 不可执行; * MEOW_ERR_NOMEM=槽满;MEOW_ERR_IO=fork 失败。同 svc 已熔断槽重注册=复位熔断重新拉起。 */ int meow_svc_register(const char *app_id, const char *svc, const char *exec, int restart, int max_restart, int window_ms, int on_card, int reg_fd); /* 请求停止服务:SIGTERM,标记 stopping(不触发重启)。返回 0=已受理;MEOW_ERR_NOENT=无此服务。 */ int meow_svc_stop(const char *app_id, const char *svc); /* 查服务槽(只读);无返回 NULL。 */ const meow_svc_rt_t *meow_svc_find(const char *app_id, const char *svc); /* 周期调:回收死亡子进程;按 restart/退避档 1/2/4/10s 重启;滑窗复位计数;超 * max_restart 熔断(notif_clear_app + send_frame MSG_SYS{SVC_DEAD} + circuit=1); * 卡拔:on_card 槽 SIGTERM + stopping=1(exec 随卡消失,不重启防空转)。 */ void meow_svc_tick(void); /* 关机前回收:向各服务注册连接广播 MSG_SYS{SHUTDOWN}(best-effort),随后全部 * bg/svc 子进程 SIGTERM→有界等 1s→SIGKILL→有界 reap。调用方负责 unlink NOTIF_ACTIVE。 */ void meow_bgplay_kill_all(void); /* 调试打印(经 ops->log,每槽一行)。 */ void meow_bgplay_dump(const char *prefix); #endif /* MEOW_BGPLAY_H */