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
This commit is contained in:
MeowOS committed 2026-09-01 18:09:09 +08:00
1 parent 7db8197f9c
commit 5aabede0cd
13 files changed
+1461 -40

No files matched your search

+90
View File
@@ -870,6 +870,22 @@ static pthread_t ipc_pthread;
static struct meow_ipc_conn ipc_conns[MEOW_IPC_CONNS];
/* M4:连接存活跟踪(ipc_thread 单写者维护计数;主循环 meow_ipc_conn_alive 读)。
* 竞态最坏=误判一帧(best-effort 熔断广播可接受;ipc_conns 前 ipc_conn_count 项恒为
* 最新存活集,因关闭时shift压实)。 */
static volatile int ipc_conn_count = 0;
/* M4:查 fd 是否为当前存活 IPC 连接(熔断广播 MSG_SYS 投递前判定,防发到已断连接)。 */
int meow_ipc_conn_alive(int fd)
{
int n = ipc_conn_count;
if (fd < 0) return 0;
if (n > MEOW_IPC_CONNS) n = MEOW_IPC_CONNS;
for (int i = 0; i < n; i++)
if (ipc_conns[i].fd == fd) return 1;
return 0;
}
/* §10.5:待处理的 App 前台「展开通知中心」请求(ipc_dispatch 单写者置位;launch_app waitpid
* 循环 meow_notif_open_take 读取并清除)。仅当 g_app_session_active(App 前台会话)期间才置位,
* 否则回 ERR(无 App 前台,不展开)。 */
@@ -1046,6 +1062,7 @@ static void *ipc_thread(void *arg)
fds[nfds].events = POLLIN;
fds[nfds].revents = 0;
nfds++;
ipc_conn_count = nfds - 1;
logmsg("ipc: conn %d accept (nfds=%d)", c, nfds - 1);
} else {
close(c);
@@ -1066,6 +1083,7 @@ static void *ipc_thread(void *arg)
ipc_conns[j - 1] = ipc_conns[j];
}
nfds--;
ipc_conn_count = nfds - 1;
continue;
}
}
@@ -1091,6 +1109,9 @@ int meow_ipc_init(void)
g_media_ops.log = logmsg;
meow_media_init(&g_media_ops);
/* M4:注入后台保活/后台服务 ops(logmsg/sd_device_present/通知清理/熔断发帧)。 */
meow_bgplay_launcher_init();
ipc_event_fd = eventfd(0, EFD_NONBLOCK);
if (ipc_event_fd < 0)
logmsg("ipc: eventfd fail %s (10ms poll drains anyway)", strerror(errno));
@@ -1119,6 +1140,9 @@ void meow_ipc_process(void)
ipc_head++;
}
__sync_synchronize();
/* M4:后台表周期维护挂点(主循环单写者排空点,run_menu/前台会话/idle/ipc-only
* 四处循环都经此处)——drain 后台 App stdout + 回收/卡拔 + 服务退避重启。 */
meow_bg_housekeep();
}
@@ -1269,6 +1293,72 @@ static void ipc_dispatch(struct meow_ipc_msg *m)
n.app_id, n.nav_type, n.depth, n.current);
break;
}
/* M4 后台服务注册(FRAMEWORK_DESIGN §5/§9):无头服务 spawn/退避重启/熔断。
* 校验链:acct 存在且 exec 非空(NOENT)→ 权限(app.json background=true 或
* flags 含 MF_BACKGROUND,否则 PERM)→ service.<svc> 静态声明存在(NOENT)→
* meow_svc_register 拉起。成功回 MSG_SERVICE_OK(m_ack_t:code=0,status=pid);
* 失败回 MSG_ERR(仿 ACT_QUERY 模式)。注册后调用方须保持连接(熔断广播投递判定)。 */
case MSG_REGISTER: {
m_register_t r;
memset(&r, 0, sizeof r);
memcpy(&r, m->payload, m->plen < (uint16_t)sizeof r ? m->plen : (uint16_t)sizeof r);
r.app_id[MEOW_APP_ID_MAX - 1] = 0;
r.svc[15] = 0;
struct meow_acct *ac = meow_acct_find(r.app_id);
const struct meow_svc *sv = ac ? meow_acct_svc_find(ac, r.svc) : NULL;
int code = MEOW_OK;
int pid = 0;
if (!ac || !ac->exec[0])
code = MEOW_ERR_NOENT;
else if (!(meow_acct_background_perm(ac) & 1) && !(r.flags & MF_BACKGROUND))
code = MEOW_ERR_PERM;
else if (!sv)
code = MEOW_ERR_NOENT;
else {
char sexec[160];
meow_svc_exec_path(ac->exec, r.svc, sexec, sizeof sexec);
pid = meow_svc_register(r.app_id, r.svc, sexec,
sv->restart, sv->max, sv->window_ms,
strncmp(sexec, MEOWAPPS, strlen(MEOWAPPS)) == 0,
m->fd);
if (pid < 0) { code = pid; pid = 0; }
}
if (code == MEOW_OK) {
m_ack_t a;
a.code = MEOW_OK;
a.status = (uint32_t)pid; /* 服务子进程 pid */
ipc_send(m->fd, MSG_SERVICE_OK, m->req_id, &a, sizeof a);
logmsg("ipc: REGISTER app=%s svc=%s -> SERVICE_OK pid=%d", r.app_id, r.svc, pid);
} else {
m_err_t e;
memset(&e, 0, sizeof e);
e.code = (uint32_t)code;
const char *msg = (code == MEOW_ERR_PERM) ? "no background permission"
: (code == MEOW_ERR_EXISTS) ? "service already running"
: (code == MEOW_ERR_NOMEM) ? "service slots full"
: "app/service not found";
e.msg_len = (uint16_t)strlen(msg);
memcpy(e.msg, msg, e.msg_len);
ipc_send(m->fd, MSG_ERR, m->req_id, &e, sizeof e);
logmsg("ipc: REGISTER app=%s svc=%s -> ERR(%d)", r.app_id, r.svc, code);
}
break;
}
/* M4:请求停止自己已注册的无头服务(SIGTERM,标记 stopping 不触发退避重启)。 */
case MSG_SERVICE_STOP: {
m_service_stop_t s;
memset(&s, 0, sizeof s);
memcpy(&s, m->payload, m->plen < (uint16_t)sizeof s ? m->plen : (uint16_t)sizeof s);
s.app_id[MEOW_APP_ID_MAX - 1] = 0;
s.svc[15] = 0;
int rc = meow_svc_stop(s.app_id, s.svc);
m_ack_t a;
a.code = (uint32_t)(rc < 0 ? -rc : MEOW_OK);
a.status = 0;
ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a);
logmsg("ipc: SERVICE_STOP app=%s svc=%s -> ACK code=%u", s.app_id, s.svc, a.code);
break;
}
/* AMSD_DESIGN §4.3:amsd `am start`/`am kill` 控制请求。主循环单写者仅置位 pending
* (run_menu 前台循环 / launch_* waitpid 循环消费),不回执启动结果(一期 best-effort)。 */
case MSG_AMS_START: {