> Linux教程 > linux基础 >

什么是systemd

systemd即为system daemon,是linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证下开源发布,开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序。

与多数发行版使用的System V风格init相比,systemd采用了以下新技术:
  (1) 采用Socket激活式与总线激活式服务,以提高相互依赖的各服务的并行运行性能;
  (2) 用Cgroups代替PID来追踪进程,以此即使是两次fork之后生成的守护进程也不会脱离systemd的控制。

systemd已纳入众多Linux发行版的软件源中,以下简表:
    Fedora 15及后续版本
    Mageia 2
    Mandriva 2011
    openSUSE 12.1及后续版本
    Arch Linux在2012年10月13日将systemd-sysvcompat纳入base软件组,自此Arch Linux默认安裝完即以systemd为init程序[13],同时也提供了与Arch自带启动脚本兼容用的systemd启动脚本包以方便用户,使用户能“开箱即用”

作用

作为init软件,systemd程序的任务有如下工作
    (1) 初始化文件系统,设置环境变量
    (2) 挂载硬盘,/proc, /tmp,  swap等等
    (3) 根据设置的运行级别, 启动相应的守护进程
    (4) 并在系统运行期间,监听整个文件系统

systemd 开启和监督整个系统是基于 unit 的概念。unit 是由一个与配置文件对应的名字和类型组成的. 一个unit配置文件,封装了后台服务,socket,设备,挂载,自动挂载,交换文件或分区,启动目标,文件系统路径,或者定时器控制,这些其中的一种。配置文件语法源于XDG Desktop Entry Specification的.desktop文件,微软的.ini文件语法格式。

所有的unit文件都应该配置[Unit]或者[Install]段.由于通用的信息在[Unit]和[Install]中描述,每一个unit应该有一个指定类型段,例如[Service]来对应后台服务类型unit.

unit 类型如下:
    service :守护进程的启动、停止、重启和重载是此类 unit 中最为明显的几个类型。
    socket :此类 unit 封装系统和互联网中的一个socket。当下,systemd支持流式, 数据报和连续包的AF\_INET,AF\_INET6,AF\_UNIX socket 。也支持传统的 FIFOs 传输模式。每一个 socket unit 都有一个相应的服务 unit 。相应的服务在第一个“连接”进入 socket 或 FIFO 时就会启动(例如:nscd.socket 在有新连接后便启动 nscd.service)。
    device :此类 unit 封装一个存在于 Linux 设备树中的设备。每一个使用 udev 规则标记的设备都将会在 systemd 中作为一个设备 unit 出现。udev 的属性设置可以作为配置设备 unit 依赖关系的配置源。
    mount :此类 unit 封装系统结构层次中的一个挂载点。
    automount :此类 unit 封装系统结构层次中的一个自挂载点。每一个自挂载 unit 对应一个已挂载的挂载 unit (需要在自挂载目录可以存取的情况下尽早挂载)。
    target :此类 unit 为其他 unit 进行逻辑分组。它们本身实际上并不做什么,只是引用其他 unit 而已。这样便可以对 unit 做一个统一的控制。(例如:multi-user.target 相当于在传统使用 SysV 的系统中运行级别5);bluetooth.target 只有在蓝牙适配器可用的情况下才调用与蓝牙相关的服务,如:bluetooth 守护进程、obex 守护进程等)
    snapshot :与 target unit 相似,快照本身不做什么,唯一的目的就是引用其他 unit 。
 

配置文件

所有配置文件存放的目录可以在以下任一目录之中

/etc/systemd/system
/etc/systemd/system/
/usr/lib/systemd/system/
/lib/systemd/system/

 加载的第一个文件为default.target, systemd的启动逻辑顺序默认如下

 
local-fs-pre.target
         |
         v
(various mounts and   (various swap (various cryptsetup
 fsck services...)     devices...)      devices...)     (various low-level 
         |                  |                |           services: udevd,  
         v                  v                v           tmpfiles, random   
  local-fs.target      swap.target   cryptsetup.target  seed, sysctl, ...) 
         |                  |                |                  |
         \__________________|_______________ | _________________/
                                            \|/
                                             v
                                      sysinit.target
                                             |
                             _______________/|\___________________
                            /                |                    \
                            |                |                    |
                            v                |                    v
                        (various             |              rescue.service
                       sockets...)           |                    |
                            |                |                    v
                            v                |              rescue.target
                     sockets.target          |
                            |                |
                            \_______________ |
                                            \|
                                             v
                                       basic.target
                                             |
            ________________________________/|             emergency.service
           /                |                |                     |
           |                |                |                     v
           v                v                v              emergency.target
       display-      (various system  (various system
   manager.service       services         services)
           |           required for          |
           |          graphical UIs)         v
           |                |         multi-user.target
           |                |                |
           \_______________ | _______________/
                           \|/
                            v
                    graphical.target (如果,我们的启动级别为5, 那么我们可以将 default.target 链接到graphical.target上)
 

例如我们需要添加一个服务, 应用程序为example, 那么我们在目录/usr/lib/systemd/system/下创建文件example.service

[Unit]
Description=Example Service
[Service]
ExecStart=/usr/bin/example
[Install]
WantedBy=multi-user.target

如果我们监视该程序,当程序退出时,能够自动重新启动。那么我们可以设置如下

 
[Unit]
Description=Example Service
[Service]
ExecStart=/usr/bin/example
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
 

Restart=可选项为
  no:程序退出不重启
  on-success:正常退出时,终止并返回退出码为0时重启
  on-failure:终止并返回退出码为非0时重启
  on-abort:非正常退出时重启,如段错误,看门狗超时等
  always:任何情况下都重启。

如果我们要求该服务在某些服务启动之后,才能启动。比如要求在dbus.service启动之后,
再启动我们的example.service,我们需要设置如下.

 
[Unit]
Description=Example Service
After=dbus.service
[Service]
ExecStart=/usr/bin/example
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
 

执行一个脚本

如我们需要在开机初始时,执行一个shell脚本,并且我们在脚本内部会启动某些后台服务程序。那么我们在写service文件的时候,则需要加入Type=forking段,否则shell执行结束退出后,
该脚本启动的后台服务程序,也会结束.

 
[Unit]
Description=panda launcher
Wants=syslog.target dbus.service

[Service]
Type=forking
ExecStart=/usr/bin/panda-launcher

[Install]
Alias=display-manager.service
WantedBy=graphical.target
 

Service的Type几种类型介绍
    simple模式 :ExecStart=设置的程序为服务的主进程,这种模式下,如果该进程为其他进程提供通信管道,应该在服务进程启动之前安装好通信管道(例如:systemd需要的socket),systemd会立即启动接下来的unit.
    forking模式:将会调用fork()函数执行ExecStart=配置的主程序,当启动和通信管道建立完成后,父进程则退出。
    idle模式 :和simple模式相似,但是实际上执行启动程序延迟到所有的job处理完成之后。
    注:默认为simple模式

特殊的unit

一些unit被systemd特别的处理。他们有特别的内部语法,并且不能修改名称。basic.target, bluetooth.target, ctrl-alt-del.target, cryptsetup.target, dbus.service, dbus.socket, default.target, 等等.

调试

根据类型列举出当前的状态

 
systemctl list-units -t service --all
列举出所有的service和他们的当前状态

systemctl status sshd.service
检查当前运行中服务的状态

systemctl show -p "Wants" multi-user.target
列出一个target组合着哪些service.

systemd的mount会去根据/etc/fstab内容,进行挂载
 

函数分析

 

 
               +---------+
               | manager |
               +---------+
                    |
    +-------------------------------+
    |           |          |        |
+-------+   +------+   +------+   +------+
|service|   |target|   |socket|   | ...  |
+-------+   +------+   +------+   +------+
    |           |          |        |
    +-------------------------------+
                    |
               +---------+      +-----+
               |  unit   |<---->| job |
               +---------+      +-----+
 

 

main中的manager

 
            +-------------+
         +->| manager_new |  set path
         |  +-------------+
         |
         |  +-----------------+
         +->| manager_startup |  read file name
         |  +-----------------+
         |
+------+ |  +-------------------+                     
| main |-+->| manager_load_unit |  anaylse file
+------+ |  +-------------------+
         |
         |  +-----------------+
         +->| manager_add_job |  add each target to job
         |  +-----------------+ 
         |
         |  +--------------+
         +->| manager_loop |
            +--------------+
 

设置路径部分代码

 
int main()
{
    if ((r = manager_new(arg_running_as, &m)) < 0) {
        goto finish;
    }
}

int manager_new(ManagerRunningAs running_as, Manager **_m) {
    if ((r = lookup_paths_init(&m->lookup_paths,
                               m->running_as, true)) < 0)
        goto fail
}

int lookup_paths_init(LookupPaths *p, ManagerRunningAs running_as,
                      bool personal) {
    if (running_as == MANAGER_USER) {
    } else 
       if (!(p->unit_path = strv_new(
           /* If you modify this you also want to modify
            * systemdsystemunitpath= in systemd.pc.in! */
           "/run/systemd/system",
           SYSTEM_CONFIG_UNIT_PATH,
           "/etc/systemd/system",
           "/usr/local/lib/systemd/system",
           "/usr/lib/systemd/system",
           SYSTEM_DATA_UNIT_PATH,
           "/lib/systemd/system",
           NULL)))
}
 

加载文件流程图

 
+------+      +-------------+   +-------------------+   +-----------------+
| main |------| manager_new |---| lookup_paths_init |---| m->lookup_paths |
+------+   |  +-------------+   +-------------------+   +-----------------+
           |  +-----------------+                           set path
           +->| manager_startup |
              +-----------------+
                       |
         +-------------------------------+
         | manager_build_unit_path_cache |
         +-------------------------------+
                       |
   +--------------------------------------------+
   | STRV_FOREACH(i, m->lookup_paths.unit_path) |
   |   set_put(m->unit_path_cache, p)           |-------unit_path_cache
   +--------------------------------------------+
                                             +-------------+
                                             | iterate_dir |
                                             +-------------+
                                                    |
     +------------------------------------------------+
     | set_get(u->manager->unit_path_cache, filename) |
     +------------------------------------------------+
          |
  +----------------+
  | load_from_path |
  +----------------+
          |                                          |.wants .requires
+--------------------+                      +------------------+
| unit_load_fragment |                      | unit_load_dropin |
+--------------------+                      +------------------+
          |                                          |
          +------------------------------------------+
                                |
           +------------------------------------+---------------+
           |                                    |               |
+-------------------------------+       +--------------+
| unit_load_fragment_and_dropin |       | service_load |     ......
+-------------------------------+       +--------------+
 
 
main()
{
    /* Initialize default unit */
    if (set_default_unit(SPECIAL_DEFAULT_TARGET) < 0)
        goto finish;

    // arg_default_unit is "default.target" 
    manager_load_unit(m, arg_default_unit, NULL, &error, &target)) < 0) 

    if ((r = manager_add_job(m, JOB_START, target, JOB_REPLACE,
                             false, &error, NULL)) < 0) {
        goto finish;
    }
}

int manager_load_unit()
{
    /* This will load the service information files, but not actually
     * start any services or anything. */
    if ((r = manager_load_unit_prepare(m, name, path, e, _ret)) != 0) 
        return r;

    manager_dispatch_load_queue(m);
}

unsigned manager_dispatch_load_queue(Manager *m) {
    while ((meta = m->load_queue)) {
        unit_load((Unit*) meta);
    }
}

int unit_load(Unit *u) {
    if (u->meta.in_load_queue) {
        //remove unit frome load_queue
        LIST_REMOVE(Meta, load_queue, u->meta.manager->load_queue,
                    &u->meta);
        u->meta.in_load_queue = false;
     }

    if (UNIT_VTABLE(u)->load)
        if ((r = UNIT_VTABLE(u)->load(u)) < 0)
             goto fail;
    
    if (u->meta.load_state == UNIT_LOADED &&
         u->meta.default_dependencies)
        if ((r = unit_add_default_dependencies(u)) < 0)
           goto fail;
}

//take service load for example
static int service_load(Unit *u) {
    /* Load a .service file */ 
    if ((r = unit_load_fragment(u)) < 0)
        return r;

    /* We were able to load something, then let's add in the
     * dropin directories. */
    if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
        return r;
}

int unit_load_fragment(Unit *u) {
    if ((r = load_from_path(u, u->meta.id)) < 0)
        return r;
}

static int load_from_path(Unit *u, const char *path) {
    if (path_is_absolute(path)) { 
    } else {
        STRV_FOREACH(p, u->meta.manager->lookup_paths.unit_path) {
            if (!(filename = path_make_absolute(path, *p))) {
            }
        }
    }
}
 

执行

 
+--------------+     +----------------------------+
| manager_loop |---->| manager_dispatch_run_queue |
+--------------+     +----------------------------+
                                   |
                       +--------------------------+
                       | if(j = m->run_queue)     |
                       |   job_run_and_invalidate |
                       +--------------------------+
                                   |
+----------------------------------------------+
|LIST_REMOVE(Job,...,j->manager->run_queue, j);|  +---------------------+
|               unit_start                     |->|UNIT_VTABLE(u)->start|
+----------------------------------------------+  +---------------------+
                                                           |
                                            +-----------------------+
+---------------------+   +-------------+   |    service_vtable = { |
| service_enter_start |<--|service_start|<--|.suffix = ".service"   |
+---------------------+   +-------------+   |.start = service_start,|
          |                                 |};                     |
    +---------------+                       +-----------------------+
    | service_spawn |
    +---------------+
          |
     +------------+
     | exec_spawn |
     +------------+
 

并发

    例如 SysV的系统启动,一次只能启动一个进程,例如将会有如下的启动顺序, Syslog -> D-Bus -> Avahi -> Blutooth. 一些版本试图改进严格的按照序列化启动,如Avahi和Bluetooth彼此独立,他们能够并发的启动。但是这个并发改进的效果并不明显.
    Socket激活,使得同时并发四个服务没有任何顺序排列成为可能,既然特定的监听socket从后台中拿出来,我们就可以同时启动他们,并且他们能够立即互相连接. 如,第一步,创建/dev/log和/run/dbus/system\_bus\_socket这两个socket,然后同时启动四个服务.当D-Bus想记录log到syslog时,就将消息写入到/dev/log中,只要socket buffer没有运行满,就不能够立即执行,并继续进行他的初始化.当syslog服务启动,他将会处理socket buffer中的队列消息.如果socket buffer满了,那么client的logging将会被阻塞,直到socket再次可写.
    Socket激活还需要service能够从systemd接收预先初始化的socket. 来代替内部初始化.
    例如: dbus-daemon, 需要进行修改,源代码中dbus/sd-daemon.c的内容和 systemd/src/sd-daemon.c代码相同。

参考

http://0pointer.de/blog/projects/systemd-docs.html
http://en.wikipedia.org/wiki/Systemd
http://fedoraproject.org/wiki/Systemd/zh-cn




(责任编辑:IT)