• systemd分析


    什么是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

  • 相关阅读:
    Entityframework的简单应用
    vs2012搭建gtest环境
    Just Me
    git简洁命令列表
    自定义控件(3)
    自定义控件(2)
    自定义控件(1)- 标签页
    使用AppleScript实现一个批量添加文件前缀的功能
    Win10下Wifi不能自动连接的问题
    Android利用Ksoap2调用Webservice时接收参数为空的问题
  • 原文地址:https://www.cnblogs.com/cfox/p/2888759.html
Copyright © 2020-2023  润新知