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 启动某个应?
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 公共控件
}
};
类关系如下:
文章图片
关键流程分析 初始化流程 ?户需先?确定好?种启动 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 的连接。
文章图片
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 的启动机制,应?的可执?程序需要是 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 机制,节约系统内存
?
推荐阅读
- 赢在人生六项精进二阶Day3复盘
- 六项精进20180530
- 2020-12(完成事项)
- 17|17 关山松 第二课作业#公众号项目# D20
- RxJava|RxJava 在Android项目中的使用(一)
- Hacking|Hacking with iOS: SwiftUI Edition - SnowSeeker 项目(一)
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- 靠QQ月入上万灰色暴利偏门的项目
- Java内存泄漏分析系列之二(jstack生成的Thread|Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析)
- [源码解析]|[源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)