mapplauncherd|mapplauncherd 项?解析【鲸鲮JingOS】

mapplauncherd 是 sailfishos 使?的?种应?启动加速的模块,类似于 Android 的 zygote。最初 mapplauncherd 是由 MeeGo 开发,后被各 Linux based 系统?于应?启动的模块。本?主要分析 mapplauncherd 的基本运?原理
源码参考 https://github.com/sailfishos/mapplauncherd.git
编译

安装依赖 sudo apt-get install libcap-dev libsystemd-dev libdbus-1-dev mkdir build && cd build cmake ../ make

使用方法
# 安装 cd build mkdir testbin DESTDIR=./testbin make install# 运? daemon LD_LIBRARY_PATH=./usr/local/lib ./usr/local/libexec/mapplauncherd/booster-generic# 再打开另?个 terminal 运? invoker ./usr/local/bin/invoker -t generic /path/to/exec

源码分析 文件布局,关键文件解释
invoker ?录,?来将应?信息传递给 launcher daemon 的?具 launcherlib ?录,其中定义了核?的功能类 appdata 应?信息 booster 启动加速类 connection 连接管理 daemon 守护进程

框架简述 mapplauncherd 整体上分为两个部分
  • daemon service,主控服务,其作?是整体管控应?的启动、结束、异常等流程
  • invoker,应?启动?具,?来通知 daemon service 启动某个应?
基础类说明 Daemon 类,对 daemon 基础功能的封装,是 mapplauncherd 的主控模块,Daemon 服务进程负责 fork 出 booster 加速进程。
SocketManager ?来管理 Booster 监听的 socket ?件,该 socket ?于 invoker 发送应?启动请求。
Booster 类,对于所有 booster 类型的抽象,顾名思义,这 booster 是来?做应?启动加速的基类,?被启动应??般会被分成?种类型,如 Qt/QML 应?,普通 native 应?,或?户?定义类型的应?。其能够加速的原因就是 Booster 预加载了某些公共资源,如QML 控件、公共库等,根本上提?了应?的启动速度。Booster 进程还?于接收 invoker 发送来的启动请求。
我们可以创建?个新的继承? Booster 基类的 JBooster 类,?来加载 JingOS ?定义的公共组件。
示例如下:
class JBooster : public Booster { public: JBooster() {} protected: bool preload() { // 加载公共库?件 // 加载 QML 公共控件 } };

类关系如下:
mapplauncherd|mapplauncherd 项?解析【鲸鲮JingOS】
文章图片


关键流程分析 初始化流程 ?户需先?确定好?种启动 booster daemon 服务的?法,如利? systemd 机制开机?启动。
?先创建?定义 Booster,即 JBooster,然后创建 Daemon 类对象,将 JBooster 对象传? Daemon。
Daemon 构造函数中创建?个 socketpair,?来与 fork 出来的 booster 加速进程通信,具体通信的内容会在后?中介绍。
Daemon 构造后调? run 进?主循环,为?进程(booster进程)创建?于接收 invoker 请求的 socket,其实在 booster 进程 fork 之后再创建这个 socket 也是可以的,mapplauncherd 在 Daemon 进程中就将 socket 创建好也应该是为了加速的?的。
资源准备好后开始 fork booster ?进程,?进程对 Booster 类对象进?初始化,主要设置两个 socket,与?进程通信的newBoosterLauncherSocket和?于接收 invoker 请求的 socketFd
初始化结束即进?主循环等待 invoker 的连接。
mapplauncherd|mapplauncherd 项?解析【鲸鲮JingOS】
文章图片


signal 信号处理流程 如果?户 kill daemon 进程的话,mapplauncherd 需要做怎样的处理呢? 在 Daemon 构造函数中定义了信号处理函数
以下代码仅展示 signal 处理相关的内容
Daemon::Daemon(int &argc, char *argv[]) {// Install signal handlers. The original handlers are saved // in the daemon instance so that they can be restored in boosters. setUnixSignalHandler(SIGCHLD, write_to_signal_pipe); // reap zombies setUnixSignalHandler(SIGINT, write_to_signal_pipe); // exit launcher setUnixSignalHandler(SIGTERM, write_to_signal_pipe); // exit launcher setUnixSignalHandler( SIGUSR1, write_to_signal_pipe); // enter normal mode from boot mode setUnixSignalHandler( SIGUSR2, write_to_signal_pipe); // enter boot mode (same as --boot-mode) setUnixSignalHandler(SIGPIPE, write_to_signal_pipe); // broken invoker's pipe setUnixSignalHandler(SIGHUP, write_to_signal_pipe); // re-exec }

信号统?由 write_to_signal_pipe 函数处理
static void write_to_signal_pipe(int sig) { char v =(char) sig; if (write(Daemon::instance()->sigPipeFd(), &v, 1) != 1) { /* If we can't write to internal signal forwarding * pipe, we might as well quit */ const char m[] = "*** signal pipe write failure - terminating\n"; if (write(STDERR_FILENO, m, sizeof m - 1) == -1) { // dontcare } _exit(EXIT_FAILURE); }

write_to_signal_pipe 函数很简单,只是向 sigPipeFd() 中写?具体是什么信号,pipe 是在 Daemon 构造函数中创建,读端已经加?到了 poll set 中,写?时即触发 poll,处理相应的信号。这样处理的原因是在 signal handler 中最好不要做太多的逻辑处理,更不能操作 heap memory,如 malloc 之类的调?,这样会导致死锁,详?《Unix 环境?级编程》中的讲解。
Daemon 是系统关键服务,如果它退出之后需要将所有经由 booster 启动的应?退掉。
case SIGINT: case SIGTERM: { for (; ; ) { // 遍历所有 booster 进程 pid PidVect::iterator iter(m_children.begin()); if (iter == m_children.end()) // 遍历结束后 break 出循环 break; pid_t booster_pid = *iter; /* Terminate booster */ kill_process("booster", booster_pid); }Logger::logDebug("booster exit"); // Daemon 进程退出 exit(EXIT_SUCCESS); break; }

当接收到应?进程退出的信号后回收?进程,即调? waitpid
case SIGCHLD: reapZombies(); break;

invoker 请求流程 桌?启动应?实质上是调? invoker 命令,invoker 的参数中包含需要启动应?的可执?程序,如?章前?介绍的使??法的中提到的。
invoker 连接 booster 的 socket ?件,将需要启动的应?的可执??件路径传给 booster,booster 需要为应?准备沙盒环境,如uid等配置,出于安全??的考虑,需要指定应?可以具有的能?,?切就绪后开始加载 main 函数。
最后向 daemon 发送启动成功的信息,daemon 再次启动?个 booster ?于?次应?的启动请求。
mapplauncherd|mapplauncherd 项?解析【鲸鲮JingOS】
文章图片

应?启动流程 为了?持 mapplauncherd 的启动机制,应?的可执?程序需要是 shared object ?不能是 executable,这就需要在编译时加? -pie (position independent executable) 选项(gcc ),并将 main 函数 export 出来。
【mapplauncherd|mapplauncherd 项?解析【鲸鲮JingOS】】加载应? main 函数的过程如下:
void *Booster::loadMain() { // Setup flags for dlopen int dlopenFlags = RTLD_LAZY; if (m_appData->dlopenGlobal()) dlopenFlags |= RTLD_GLOBAL; else dlopenFlags |= RTLD_LOCAL; #if (PLATFORM_ID == Linux) && defined( GLIBC ) if (m_appData->dlopenDeep()) dlopenFlags |= RTLD_DEEPBIND; #endif// 打开 invoker 发送过来的可执?程序 void *module = dlopen(m_appData->fileName().c_str(), dlopenFlags); dlerror(); // 导出 main 函数 m_appData->setEntry(reinterpret_cast(dlsym(module, "main"))); const char *error_s = dlerror(); if (error_s != NULL) throw std::runtime_error( std::string("Booster: Loading symbol 'main' failed: '") + error_s + "'\n"); return module; }~ ~

执行过程
int Booster::launchProcess() { setEnvironmentBeforeLaunch(); // 加载 main 函数 loadMain(); // 调? main 函数 const int retVal = m_appData->entry()(m_appData->argc(), const_cast(m_appData->argv())); return retVal; }

方案的优点
  • 开发者可?定义 Booster 类,定制需要预加载的资源及应?启动前的准备流程
  • 可配置沙盒及能?控制,确保系统的安全
  • 利? fork 系统调?的 COW 机制,节约系统内存
改进思路 个?感觉 zygote 的结构更好合理,mapplauncherd 的 daemon 服务完全可以作为应?孵化器,监听所有 invoker 请求,当有 invoker 请求连?时才开始 fork ?进程。?不是预先 fork 出?个 booster,在 load main 函数之后再让 daemon fork 另?个 booster,这个过程感觉上是多余的,zygote 的流程更加简洁。
?

    推荐阅读