Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/meow_bgplay.c
T
MeowOS 5aabede0cd feat(launcher): M4 后台服务保活+后台播放(FRAMEWORK_DESIGN §5/§9 落地)
- app.json 新字段 background/background_audio 解析入 struct app/meow_acct 注册表
- meow_ipc.h:MSG_REGISTER(m_register_t)/SERVICE_OK(m_ack_t.status=pid)/SERVICE_STOP/
  MSG_SYS(m_sys_t, SHUTDOWN=1/SVC_DEAD=2) 负载结构落地
- 新模块 meow_bgplay.c/h(自包含+ops 注入,仿 meow_media,宿主可单测):
  · 后台播放保活——声明 background_audio 且媒体会话 PLAYING 的 App nav-exit 不终止,
    进程+音频流保留;UI 冻结走 §10.5 /tmp/meow_notif_active SDK present 自抑(非字面
    SIGSTOP:冻结同进程播放线程断供 PCM,§10.5 真机实证);点图标/媒体卡片回前台=
    原进程恢复(chain/picker 忽略),停播后再退出=真退 SIGTERM
  · 无头后台服务——MSG_REGISTER spawn/stop/reap + 退避重启 1/2/4/10s cap +
    max_restart 熔断(清该 App 通知 + 注册连接广播 MSG_SYS{SVC_DEAD},重注册复位)
  · 卡拔例外(卡上二进制后台 App/服务仍 SIGTERM 防 SIGBUS)、崩溃照收、
    关机前 meow_bgplay_kill_all 有界回收(含 MSG_SYS{SHUTDOWN} 广播)
- util.c:MSG_REGISTER/MSG_SERVICE_STOP 分发(权限/声明校验链)+ ipc_conn_alive 连接
  存活查询 + meow_ipc_process 末尾挂 meow_bg_housekeep(drain/reap/退避 tick)
- app_launcher.c:launch_app_chain 入口 resume(bg_take 原进程回前台)+ nav-exit
  保活分支 + 转后台出口(杀看门狗/写自抑标记/复位顶条,跳过退出尾)+ 冷启动自动
  拉起静态 keepalive(mode=0) 服务(exec 路径约定=App 同目录 <svc> 文件)
- main_menu.c:应用列表「·后台」标注(宫格/列表,1s tick 按 change_seq 刷新)+
  两条退出尾 kill_all + unlink NOTIF_ACTIVE
- 宿主单测 test_bgplay.c 50 项全过(真 fork 子进程走状态机 + 假时钟退避熔断);
  test_media/test_notif_panel/test_notif_tf/test_vol_ramp_jack 回归全过;
  zig-mipsel-cc -Wall -Wextra 0 warning。launcher md5=c07d011c0769fca14d3bfb752d735463
2026-09-01 18:09:09 +08:00

510 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* meow_bgplay.c — M4 后台播放保活 + 无头后台服务运行时表(见 meow_bgplay.h 头注释)。 */
#include "meow_bgplay.h"
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
static meow_bgplay_ops g_ops;
static meow_bg_entry_t g_bg[MEOW_BG_MAX];
static meow_svc_rt_t g_svc[MEOW_SVC_RT_MAX];
static uint32_t g_change_seq;
static const uint32_t k_backoff[] = {
MEOW_SVC_BACKOFF_1, MEOW_SVC_BACKOFF_2, MEOW_SVC_BACKOFF_4, MEOW_SVC_BACKOFF_10
};
#define BACKOFF_STEPS ((int)(sizeof(k_backoff) / sizeof(k_backoff[0])))
/* SIGTERM 后 SIGKILL 兜底宽限(ms) */
#define TERM_GRACE_MS 500u
/* ------------------------------------------------------------------ */
static void bg_log(const char *fmt, ...)
{
va_list ap;
char buf[384];
if (!g_ops.log) return;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
g_ops.log("%s", buf);
}
static uint64_t bg_now_ms(void)
{
struct timespec ts;
if (g_ops.now_ms) return g_ops.now_ms();
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u;
}
static int bg_card_present(void)
{
return g_ops.card_present ? g_ops.card_present() : 1;
}
void meow_bgplay_init(const meow_bgplay_ops *ops)
{
memset(&g_ops, 0, sizeof(g_ops));
memset(g_bg, 0, sizeof(g_bg));
memset(g_svc, 0, sizeof(g_svc));
g_change_seq = 0;
if (ops) g_ops = *ops;
}
/* ================= 后台播放保活 ================= */
int meow_bg_should_keep(int bg_audio_declared, int media_playing)
{
return bg_audio_declared && media_playing;
}
static meow_bg_entry_t *bg_find(const char *app_id)
{
int i;
for (i = 0; i < MEOW_BG_MAX; i++)
if (g_bg[i].used && strcmp(g_bg[i].app_id, app_id) == 0)
return &g_bg[i];
return NULL;
}
int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card)
{
int i;
if (!app_id || !app_id[0] || pid <= 0) return MEOW_ERR_INVAL;
if (bg_find(app_id)) return MEOW_ERR_EXISTS;
for (i = 0; i < MEOW_BG_MAX; i++) {
if (!g_bg[i].used) {
meow_bg_entry_t *e = &g_bg[i];
memset(e, 0, sizeof(*e));
e->used = 1;
snprintf(e->app_id, sizeof(e->app_id), "%s", app_id);
e->pid = pid;
e->out_fd = out_fd;
e->on_card = on_card;
g_change_seq++;
bg_log("[bgplay] 转后台保活: %s pid=%d on_card=%d", app_id, (int)pid, on_card);
return 0;
}
}
return MEOW_ERR_NOMEM;
}
int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid)
{
meow_bg_entry_t *e;
if (!app_id || !app_id[0]) return -1;
e = bg_find(app_id);
if (!e) return -1;
if (out_fd) *out_fd = e->out_fd;
if (pid) *pid = e->pid;
bg_log("[bgplay] 回前台: %s pid=%d", app_id, (int)e->pid);
memset(e, 0, sizeof(*e));
g_change_seq++;
return 0;
}
int meow_bg_is_backgrounded(const char *app_id)
{
return app_id && app_id[0] && bg_find(app_id) != NULL;
}
int meow_bg_count(void)
{
int i, n = 0;
for (i = 0; i < MEOW_BG_MAX; i++)
if (g_bg[i].used) n++;
return n;
}
const meow_bg_entry_t *meow_bg_at(int i)
{
if (i < 0 || i >= MEOW_BG_MAX || !g_bg[i].used) return NULL;
return &g_bg[i];
}
uint32_t meow_bgplay_change_seq(void)
{
return g_change_seq;
}
void meow_bg_tick(void)
{
uint64_t now = bg_now_ms();
int i;
for (i = 0; i < MEOW_BG_MAX; i++) {
meow_bg_entry_t *e = &g_bg[i];
int st;
pid_t r;
if (!e->used) continue;
/* 卡拔例外:二进制在卡上 → SIGTERM(防 SIGBUS),500ms 未死 SIGKILL */
if (e->on_card && !bg_card_present()) {
if (e->term_at_ms == 0) {
bg_log("[bgplay] 卡拔出,终止卡上后台 App: %s pid=%d",
e->app_id, (int)e->pid);
kill(e->pid, SIGTERM);
e->term_at_ms = now;
} else if (now - e->term_at_ms >= TERM_GRACE_MS) {
kill(e->pid, SIGKILL);
e->term_at_ms = now; /* 500ms 节流重复 SIGKILL 直至 reap */
}
}
r = waitpid(e->pid, &st, WNOHANG);
if (r == e->pid) {
char id[MEOW_APP_ID_MAX];
pid_t pid = e->pid;
snprintf(id, sizeof(id), "%s", e->app_id);
memset(e, 0, sizeof(*e));
g_change_seq++;
bg_log("[bgplay] 后台 App 退出: %s pid=%d status=0x%x", id, (int)pid, st);
if (g_ops.on_bg_dead) g_ops.on_bg_dead(id, pid, st);
}
}
}
/* ================= 无头后台服务 ================= */
static meow_svc_rt_t *svc_find_mut(const char *app_id, const char *svc)
{
int i;
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
if (g_svc[i].used && strcmp(g_svc[i].app_id, app_id) == 0 &&
strcmp(g_svc[i].svc, svc) == 0)
return &g_svc[i];
return NULL;
}
const meow_svc_rt_t *meow_svc_find(const char *app_id, const char *svc)
{
if (!app_id || !svc) return NULL;
return svc_find_mut(app_id, svc);
}
/* spawn 服务子进程:stdio→/dev/null,env={PATH,HOME,MEOW_SERVICE[,MEOW_LOG_DIR/MEOW_LOG_APP]}。
* 父进程返回 pid;子进程不返回(execve 或 _exit(127))。 */
static pid_t svc_spawn(meow_svc_rt_t *s)
{
char env_service[32];
char env_logdir[160];
char env_logapp[64];
char *envp[6];
int n = 0;
pid_t pid;
envp[n++] = (char *)"PATH=/usr/bin:/bin:/usr/sbin:/sbin";
envp[n++] = (char *)"HOME=/root";
snprintf(env_service, sizeof(env_service), "MEOW_SERVICE=%s", s->svc);
envp[n++] = env_service;
if (g_ops.log_dir && g_ops.log_dir[0]) {
snprintf(env_logdir, sizeof(env_logdir), "MEOW_LOG_DIR=%s", g_ops.log_dir);
envp[n++] = env_logdir;
snprintf(env_logapp, sizeof(env_logapp), "MEOW_LOG_APP=%s", s->app_id);
envp[n++] = env_logapp;
}
envp[n] = NULL;
pid = fork();
if (pid != 0) return pid; /* 父:pid 或 -1 */
/* 子进程:脱离终端会话,stdio 全部重定向 /dev/null(无头服务) */
setsid();
{
int dn = open("/dev/null", O_RDWR);
if (dn >= 0) {
dup2(dn, STDIN_FILENO);
dup2(dn, STDOUT_FILENO);
dup2(dn, STDERR_FILENO);
if (dn > STDERR_FILENO) close(dn);
}
}
{
char *argv[2];
argv[0] = s->exec;
argv[1] = NULL;
execve(s->exec, argv, envp);
}
_exit(127); /* exec 失败:父进程 tick 回收后按退避/熔断处理 */
}
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)
{
meow_svc_rt_t *s;
pid_t pid;
int i;
if (!app_id || !app_id[0] || !svc || !svc[0] || !exec || !exec[0])
return MEOW_ERR_INVAL;
if (access(exec, X_OK) != 0) return MEOW_ERR_NOENT;
s = svc_find_mut(app_id, svc);
if (s) {
if (s->pid > 0) return MEOW_ERR_EXISTS;
if (!s->circuit) return MEOW_ERR_EXISTS; /* 退避排程中也视为在册 */
/* 熔断槽重注册:复位熔断,重新武装 */
bg_log("[bgplay] 服务熔断复位重注册: %s/%s", app_id, svc);
s->circuit = 0;
s->stopping = 0;
s->restart_count = 0;
s->window_start_ms = bg_now_ms();
s->next_retry_ms = 0;
s->reg_fd = reg_fd;
s->on_card = on_card;
} else {
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
if (!g_svc[i].used) break;
if (i >= MEOW_SVC_RT_MAX) return MEOW_ERR_NOMEM;
s = &g_svc[i];
memset(s, 0, sizeof(*s));
s->used = 1;
snprintf(s->app_id, sizeof(s->app_id), "%s", app_id);
snprintf(s->svc, sizeof(s->svc), "%s", svc);
snprintf(s->exec, sizeof(s->exec), "%s", exec);
s->restart = restart;
s->max_restart = max_restart;
s->window_ms = window_ms;
s->on_card = on_card;
s->reg_fd = reg_fd;
s->window_start_ms = bg_now_ms();
}
pid = svc_spawn(s);
if (pid < 0) {
if (s->pid == 0 && s->restart_count == 0 && !s->circuit)
memset(s, 0, sizeof(*s)); /* 新槽首次 fork 即败:不留尸槽 */
return MEOW_ERR_IO;
}
s->pid = pid;
s->term_at_ms = 0;
bg_log("[bgplay] 服务拉起: %s/%s pid=%d restart=%d max=%d",
app_id, svc, (int)pid, restart, max_restart);
return (int)pid;
}
int meow_svc_stop(const char *app_id, const char *svc)
{
meow_svc_rt_t *s;
if (!app_id || !svc) return MEOW_ERR_INVAL;
s = svc_find_mut(app_id, svc);
if (!s) return MEOW_ERR_NOENT;
bg_log("[bgplay] 服务停止: %s/%s pid=%d", app_id, svc, (int)s->pid);
if (s->pid > 0) {
s->stopping = 1;
kill(s->pid, SIGTERM);
s->term_at_ms = bg_now_ms();
} else {
/* 未在跑(退避中/熔断):直接清槽 */
if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc);
memset(s, 0, sizeof(*s));
}
return 0;
}
/* 服务死亡后的退避/熔断判定。返回 1=已排程重启;0=槽已清或熔断。 */
static int svc_on_exit(meow_svc_rt_t *s, int st)
{
uint64_t now = bg_now_ms();
bg_log("[bgplay] 服务退出: %s/%s pid=%d status=0x%x stopping=%d",
s->app_id, s->svc, (int)s->pid, st, s->stopping);
s->pid = 0;
if (s->stopping || !s->restart) {
if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc);
memset(s, 0, sizeof(*s));
return 0;
}
/* 滑窗:超窗复位计数 */
if (s->window_ms > 0 && now - s->window_start_ms >= (uint64_t)s->window_ms) {
s->restart_count = 0;
s->window_start_ms = now;
}
s->restart_count++;
if (s->max_restart > 0 && s->restart_count > s->max_restart) {
/* 熔断:清通知 + 广播 MSG_SYS{SVC_DEAD},槽保留待重注册复位 */
m_sys_t m;
s->circuit = 1;
s->next_retry_ms = 0;
bg_log("[bgplay] 服务熔断: %s/%s 窗口内重启 %d 次超 max=%d",
s->app_id, s->svc, s->restart_count, s->max_restart);
if (g_ops.notif_clear_app)
g_ops.notif_clear_app(s->app_id);
if (g_ops.send_frame && s->reg_fd >= 0) {
memset(&m, 0, sizeof(m));
m.event = MEOW_SYS_SVC_DEAD;
snprintf(m.app_id, sizeof(m.app_id), "%s", s->app_id);
snprintf(m.svc, sizeof(m.svc), "%s", s->svc);
g_ops.send_frame(s->reg_fd, MSG_SYS, 0, &m, sizeof(m));
}
if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc);
return 0;
}
{
int step = s->restart_count - 1;
uint32_t dly;
if (step >= BACKOFF_STEPS) step = BACKOFF_STEPS - 1;
dly = k_backoff[step];
s->next_retry_ms = now + dly;
bg_log("[bgplay] 服务退避重启: %s/%s 第 %d 次,%ums 后拉起",
s->app_id, s->svc, s->restart_count, (unsigned)dly);
}
return 1;
}
void meow_svc_tick(void)
{
uint64_t now = bg_now_ms();
int i;
for (i = 0; i < MEOW_SVC_RT_MAX; i++) {
meow_svc_rt_t *s = &g_svc[i];
if (!s->used) continue;
/* 卡拔例外:exec 在卡上 → SIGTERM + stopping=1(不重启防空转死循环) */
if (s->on_card && s->pid > 0 && !bg_card_present() && !s->stopping) {
bg_log("[bgplay] 卡拔出,终止卡上服务: %s/%s pid=%d",
s->app_id, s->svc, (int)s->pid);
s->stopping = 1;
kill(s->pid, SIGTERM);
s->term_at_ms = now;
}
/* SIGTERM 宽限超时 → SIGKILL */
if (s->stopping && s->pid > 0 && s->term_at_ms &&
now - s->term_at_ms >= TERM_GRACE_MS) {
kill(s->pid, SIGKILL);
s->term_at_ms = now;
}
if (s->pid > 0) {
int st;
pid_t r = waitpid(s->pid, &st, WNOHANG);
if (r == s->pid) svc_on_exit(s, st);
continue;
}
/* 退避到点 → 重新拉起 */
if (!s->circuit && !s->stopping && s->restart &&
s->next_retry_ms && now >= s->next_retry_ms) {
pid_t pid = svc_spawn(s);
s->next_retry_ms = 0;
if (pid > 0) {
s->pid = pid;
bg_log("[bgplay] 服务重启: %s/%s pid=%d(第 %d 次)",
s->app_id, s->svc, (int)pid, s->restart_count);
} else {
/* fork 失败:压到下一档重试 */
s->next_retry_ms = now + k_backoff[BACKOFF_STEPS - 1];
}
}
}
}
/* ================= 关机前回收 ================= */
void meow_bgplay_kill_all(void)
{
uint64_t deadline;
int i;
/* best-effort 广播 SHUTDOWN */
if (g_ops.send_frame) {
m_sys_t m;
memset(&m, 0, sizeof(m));
m.event = MEOW_SYS_SHUTDOWN;
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
if (g_svc[i].used && g_svc[i].reg_fd >= 0)
g_ops.send_frame(g_svc[i].reg_fd, MSG_SYS, 0, &m, sizeof(m));
}
for (i = 0; i < MEOW_BG_MAX; i++)
if (g_bg[i].used && g_bg[i].pid > 0) kill(g_bg[i].pid, SIGTERM);
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
if (g_svc[i].used && g_svc[i].pid > 0) kill(g_svc[i].pid, SIGTERM);
/* 有界等 1s(轮询 reap,期间 tick 语义不在——直接 waitpid) */
deadline = bg_now_ms() + 1000u;
while (bg_now_ms() < deadline) {
int alive = 0;
for (i = 0; i < MEOW_BG_MAX; i++) {
if (!g_bg[i].used) continue;
if (waitpid(g_bg[i].pid, NULL, WNOHANG) == g_bg[i].pid)
memset(&g_bg[i], 0, sizeof(g_bg[i]));
else
alive++;
}
for (i = 0; i < MEOW_SVC_RT_MAX; i++) {
if (!g_svc[i].used) continue;
if (waitpid(g_svc[i].pid, NULL, WNOHANG) == g_svc[i].pid)
memset(&g_svc[i], 0, sizeof(g_svc[i]));
else
alive++;
}
if (!alive) break;
usleep(50 * 1000);
}
/* 兜底 SIGKILL + 有界 reap(waitpid 阻塞上限由 SIGKILL 必死保证) */
for (i = 0; i < MEOW_BG_MAX; i++)
if (g_bg[i].used) {
kill(g_bg[i].pid, SIGKILL);
waitpid(g_bg[i].pid, NULL, 0);
memset(&g_bg[i], 0, sizeof(g_bg[i]));
}
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
if (g_svc[i].used) {
kill(g_svc[i].pid, SIGKILL);
waitpid(g_svc[i].pid, NULL, 0);
memset(&g_svc[i], 0, sizeof(g_svc[i]));
}
g_change_seq++;
bg_log("[bgplay] kill_all 完成(关机/退出回收)");
}
void meow_bgplay_dump(const char *prefix)
{
int i;
for (i = 0; i < MEOW_BG_MAX; i++)
if (g_bg[i].used)
bg_log("%s bg[%d] %s pid=%d on_card=%d", prefix, i,
g_bg[i].app_id, (int)g_bg[i].pid, g_bg[i].on_card);
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
if (g_svc[i].used)
bg_log("%s svc[%d] %s/%s pid=%d restart=%d count=%d circuit=%d",
prefix, i, g_svc[i].app_id, g_svc[i].svc, (int)g_svc[i].pid,
g_svc[i].restart, g_svc[i].restart_count, g_svc[i].circuit);
}