关于linux:mapplauncherd-项⽬解析鲸鲮JingOS

41次阅读

共计 4764 个字符,预计需要花费 12 分钟才能阅读完成。

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 公共控件
    }
};

类关系如下:


 

要害流程剖析

初始化流程

⽤户需先⾏确定好⼀种启动 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 进去。

加载应⽤ 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<entry_t>(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<char **>(m_appData->argv()));

  return retVal;
}

计划的长处

  • 开发者可⾃定义 Booster 类,定制须要预加载的资源及应⽤启动前的筹备流程
  • 可配置沙盒及能⼒管制,确保零碎的平安
  • 利⽤ fork 零碎调⽤的 COW 机制,节约零碎内存

改良思路

个⼈感觉 zygote 的构造更好正当,mapplauncherd 的 daemon 服务齐全能够作为应⽤孵化器,监听所有 invoker 申请,当有 invoker 申请连⼊时才开始 fork ⼦过程。⽽不是事后 fork 出⼀个 booster,在 load main 函数之后再让 daemon fork 另⼀个 booster,这个过程感觉上是多余的,zygote 的流程更加简洁。

正文完
 0