什么是systemd
时间:2014-11-21 13:06 来源:linux.it.net.cn 作者:IT
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)
systemd即为system daemon,是linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证下开源发布,开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序。
与多数发行版使用的System V风格init相比,systemd采用了以下新技术:
systemd已纳入众多Linux发行版的软件源中,以下简表: 作用
作为init软件,systemd程序的任务有如下工作
systemd 开启和监督整个系统是基于 unit 的概念。unit 是由一个与配置文件对应的名字和类型组成的. 一个unit配置文件,封装了后台服务,socket,设备,挂载,自动挂载,交换文件或分区,启动目标,文件系统路径,或者定时器控制,这些其中的一种。配置文件语法源于XDG Desktop Entry Specification的.desktop文件,微软的.ini文件语法格式。
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=可选项为
如果我们要求该服务在某些服务启动之后,才能启动。比如要求在dbus.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几种类型介绍 特殊的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彼此独立,他们能够并发的启动。但是这个并发改进的效果并不明显. 参考
http://0pointer.de/blog/projects/systemd-docs.html (责任编辑:IT) |