文档中心 > 芯片厂商开放

systemd及系统初始化

更新时间:2017/08/17 访问次数:5127

文档内容拷贝自:codebase_host/yunhal/opendoc/zh/porting_guide/systemd_and_init.md
联系人:赤铁
版本:commit-id(93385c3c9f53d946600ad2611e68dc1207761889)

Systemd及系统初始化

版本编号 修订内容 日期 修订人
0.1 初稿 2017.07.05 李卿
6.0 更新稿 2017.08.16 李卿

1 功能说明

Systemd是Linux系统中PID为1的初始化系统(init),它主要的设计目标是克服sysvinit固有的缺点,提高系统的启动速度,systemd的很多概念来源于苹果Mac OS操作系统上的launchd,不过launchd专用于苹果系统。
YunOS采用systemd作为系统的初始进程,基于上游systemd 219版本移植而来,并且针对YunOS支持多端设备的特性进行了深度定制,同时支持TV、Phone、Tablet、Car等多个平台。
在不同平台上, systemd支持YunHAL和AndroidHAL两种运行模式,使用编译开关进行区分,其中YunHAL版本的systemd是系统加载后首先启动的进程,负责整个YunOS系统的分区挂载、设备管理、Native服务启动等若干初始化工作,与此同时还支持充电模式等移动设备特性,借助systemd先进的并行启动和按需启动的特性,YunHAL上系统启动速度大大加快。

1.1 系统启动过程

Systemd作为系统首个启动的进程,使用udevd配置设备节点,利用.mount单元文件配置分区挂载、通过解析.service单元文件启动系统服务,这些工作分成不同的步骤,并暴露成target单元。Systemd启动后根据本次启动的taget(可通过kernel cmdline指定)为各个任务建立依赖关系并完成YunOS系统的初始化,在Android Runtime Plugin安装后会由systemd启动的服务pluginmanagerd完成后续整个Android系统的启动,启动过程如下图所示:
system_boot_process

2 功能移植

版本[v6.0]说明基于codebase_host分支,此分支的移植包含了TV,Phone,Tablet,Car等各平台上已合入的修改。
Systemd代码实现放在Host侧的/third_party/systemd/里,并依据宏定义区分不同的产品和HAL,进行差异化的编译处理。
例如,在.mk编译文件里SYSTEMD_HAVE_YUNOSTABLET = true表示是Pad线,XMAKE_ENABLE_CNTR_HAL = true表示是AndroidHAL:

ifeq ($(SYSTEMD_HAVE_YUNOSTABLET),true)
ifeq ($(XMAKE_ENABLE_CNTR_HAL),true)
dist_etcsystemunit_DATA += \
    units/yunos/tablet/adbd.service \
    ...
    ...
    ...
    units/yunos/tablet/devicetreemanagerd.service
else
dist_etcsystemunit_DATA += \
    units/yunos/yunhal/adbd.service \
    ...
    ...
    ...
    units/yunos/yunhal/mediaservice.service

endif
endif

2.1 Kernel启动systemd

Kernel从init切换启动systemd,需要修改boot.image的kernel cmdline:

init=/usr/lib/systemd/systemd

2.2 设备管理

Systemd使用udev管理设备。当设备添加/删除时,udevd监听来自内核的uevent,添加或者删除/dev下的设备文件,并且 通过定义一个udev规则来产生匹配设备属性的设备文件并设定设备的权限和所有者/组,这些设备属性可以是内核设备名称、总线路径、厂商名称、型号、序列号或者磁盘大小。

2.2.1 udev使能

使能udev需要以下步骤:
1. 确认内核config开关已经打开devtmpfs:CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y
2. 修改systemd代码库/yunos-mk/ yunos-systemd-config-prebuilt.mk的LOCAL_COMPILE_SHELL_CMD的sockets.target.wants里增加符号链接:

$(LN_S) ../systemd-udevd-control.socket systemd-udevd-control.socket && \
$(LN_S) ../systemd-udevd-kernel.socket systemd-udevd-kernel.socket && \
  1. 在sysinit.target.wants里增加符号链接:

    $(LN_S) ../systemd-udev-trigger.service systemd-udev-trigger.service && \
    $(LN_S) ../systemd-udevd.service systemd-udevd.service && \
  2. 删除以下几行:

    $(RM_F)  $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/usr/lib$(lib_suffix)/systemd/system/systemd-udevd-control.socket && \
    $(RM_F)  $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/usr/lib$(lib_suffix)/systemd/system/systemd-udevd-kernel.socket && \
    $(RM_F)  $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/usr/lib$(lib_suffix)/systemd/system/systemd-udev-trigger.service && \
    $(RM_F)  $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/usr/lib$(lib_suffix)/systemd/system/systemd-udev-settle.service && \
    $(RM_F)  $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/usr/lib$(lib_suffix)/systemd/system/systemd-udevd.service && \

这样编译出的系统systemd就可以利用udevd管理设备了,systemd默认管理设备是通过读取udev rule配置文件实现的,下面详细讲述udev规则。

2.2.2 udev规则

Udev规则文件位于systemd源码库/third_party/systemd/rules内,编译烧机后会安装到设备目录/usr/lib/udev/rules.d内,以“.rules”为后缀名,例如50-udev-default.rules。udev按照规则文件名的字母顺序来查询全部规则文件,然后为匹配规则的设备管理其设备文件或文件链接,通常情况下,建议让自己想要的规则文件最先被解析。比如,创建一个名为10-local.rules的文件,并把规则写入该文件,这样udev就会在解析系统默认的规则文件之前解析到这个文件。

在规则文件里,除了以“#”开头的注释行和空行外,其他所有的行都被视为一条规则,一条规则不能扩展到多行。规则是由多个 键值对组成,并由逗号分隔,键值对可分为 匹配键值 和 赋值键值,一条规则可以有多条匹配键和多条赋值键。匹配键是匹配一个设备属性的所有条件,当一个设备的属性匹配了该规则里所有的匹配键,就认为这条规则生效,然后按照赋值键的内容,执行该规则的赋值,例如:

KERNEL=="sda", NAME="test_disk", MODE="0600"

KERNEL==“sda”是匹配键,NAME=“test_disk”和MODE=“0600”是赋值键。这条规则是:如果有一个设备的内核设备名称为sda,则该条件生效,执行后面的赋值语句:在/dev下产生一个名为test_disk的设备文件,并把设备文件的权限设为0600。
目前,我们已预置了一部分规则在systemd的50-udev-default.rules文件里,若需要增加或修改规则,可参考udev规则写法:http://www.reactivated.net/writing_udev_rules.html

2.3 分区挂载

YunOS系统的分区一般有system分区、data分区、cache分区以及plugin分区组成。由于Host系统不带有ramdisk.img,因此system分区的挂载由kernel cmdline指定root=/dev/mmcblk0p7(设备不同名称可能不同) 完成;data分区和cache分区可以利用systemd提供的.mount单元文件进行配置,或直接修改systemd的mount table;plugin.img则是由服务pluginmanager挂载。

2.3.1 生成image

image的制作一般位于Host侧的/xmake/prebuilts/devtools/mk-image.sh,以intel yunhal pad产品为例包括prepare_yunhal_image和make_intel_yunhal_image两个部分。其中,prepare_yunhal_image函数的作用是为系统分区准备必要的目录和符号链接,make_intel_yunhal_image函数则是用来制作系统各个分区。

在make_intel_yunhal_image函数制作boot.img时修改cmdline:root=/dev/mmcblk0p7 ro rootwait init=/usr/lib/systemd/systemd,可在系统启动时自动挂载系统分区/dev/mmcblk0p7到根目录并使能systemd作为PID=1的进程。

2.3.2 mount单元文件

2.3.2.1 配置

Mount单元文件描述了由systemd控制的文件系统挂载点的信息,主要包含两个部分[Unit]段和[Mount]段,[Unit]段描述了描述和依赖关系,[Mount]段则描述了具体的挂载信息,以data分区单元文件mnt-data.mount为例:

[Unit]
Description=Mount YunOS Data Partition
ConditionPathIsSymbolicLink=!/mnt/data
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target

[Mount]
What=/dev/mmcblk0p9
Where=/mnt/data
Type=ext4
Options=rw,noatime,nosuid,nodev,noauto_da_alloc,discard,barrier=1,data=ordered

首先,mount单元文件命名时需要和挂载点目录一致,挂载点为/mnt/data,则mount单元文件需要命名为mnt-data.mount。
一、[Unit]段:
Description= Mount YunOS Data Partition
描述了mount单元文件的基本信息
ConditionPathIsSymbolicLink=!/mnt/data
在挂载设备前确保挂载点不是一个符号链接
DefaultDependencies=no
解除自动依赖
Conflicts=umount.target
确保umount时可自动umount挂载的设备
Before=local-fs.target umount.target
确保启动到local-fs.target前和umount.target前完成设备的挂载
二、[Mount]段:
What=/dev/mmcblk0p9
指出挂载的设备,不同的BSP上设备可能不同
Where=/mnt/data
挂载点的绝对路径
Type=ext4
挂载的文件系统类型
Options=rw,noatime,nosuid,nodev,noauto_da_alloc,discard,barrier=1,data=ordered
挂载选项,不同的文件系统可能支持不同的选项

2.3.2.2 安装

完成mount单元文件的配置后将单元文件安装到系统后方可生效,在代码中可按如下步骤安装:
1. 修改systemd源码/yunos-mk/yunos-systemd_etc_config_prebuilt.mk文件,在dist_etcsystemunit_DATA添加新的mount单元文件。
2. 修改LOCAL_COMPILE_SHELL_CMD,增加:

mkdir -p $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/etc/systemd/system/local-fs.target.wants;cd $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/etc/systemd/system/local-fs.target.wants && \
$(LN_S) ../xxx.mount xxx.mount && \
  1. 提交改动并重新build系统,这样新的系统在启动后会自动挂载单元文件描述的设备。

本地调试验证时可直接将单元文件push到/etc/systemd/system/然后去/etc/systemd/system/local-fs.target.wants/目录建立指向上级目录/etc/systemd/system/里单元文件的符号链接,重启系统即可生效。

2.3.3 修改mount_table

注意到mount单元文件一般是用来支持块设备分区的挂载,如果需要挂载的设备不是块设备,可直接修改systemd的mount table实现系统启动时分区的挂载。
具体来说,systemd的mount_table位于systemd代码/src/core/mount-setup.c的static const MountPoint mount_table[],在mount_table里添加需要挂载的信息即可,这种修改方式建议使用宏定义区分平台和产品。

需要注意,不要在配置mount单元文件的同时还修改mount_table,建议这两种方式选其一。

2.3.4 初始化目录文件属性

可读写分区挂载完成后,需要利用systemd-config-fs这个工具进行目录和文件属性的初始化,这个工具的代码位于systemd代码/src/config-fs/内,其中config-fs.h描述了系统目录和文件属性的配置信息,config-fs.c是工具的main函数。

启用该工具需要修改/yunos-mk/yunos-systemd-config-prebuilt.mk文件,在nodist_systemunit_DATA里增加units/systemd-config-fs.service,在LOCAL_COMPILE_SHELL_CMD的sysinit.target.wants里面增加符号链接$(LN_S) ../systemd-config-fs.service systemd-config-fs.service && \

后续根据产品调整目录文件属性,可修改config-fs.h文件并利用宏定义区分不同产品和平台:

155         /* yunos data folder */
156         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos" },
157         /* /data->/mnt/data/yunos/share */
158         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/share" },
159         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/share/opt" },
160         { 00755, UID_ROOT, UID_ROOT, 0, "/mnt/data/yunos/share/etc" },
161         /* /opt->/mnt/data/yunos/opt */
162         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/opt" },
163         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/opt/data" },
164         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/opt/app" },
165         /* /var->/mnt/data/yunos/var */
166         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/var" },
167         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/var/log" },
168         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/var/wayland" },
169         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/var/lib" },
170         { 00755, UID_ROOT, UID_ROOT, 0, "/mnt/data/yunos/var/tmp" },
171         { 00775, UID_SYSTEM, UID_SYSTEM, 0, "/mnt/data/yunos/var/run" },
172         /* cache folder */
173         { 00770, UID_SYSTEM, UID_CACHE, 0, "/cache" },
174         { 00770, UID_SYSTEM, UID_CACHE, 0, "/cache/recovery" },

需要注意,处于优化系统启动的目的,初始化目录文件属性的操作只会在双清烧机后的首次启动才执行,这个工具首次执行后会在data分区/var/生成config_fs.done的标记,后续的启动systemd-config-fs在check到这个标记后便直接退出,若产品对系统启动时间容忍度较高,可将这部分逻辑去除,保证每次启动都配置目录文件的属性:

76         /*
77          ** if oneshot file exists, return.
78          */
79         if (!access(oneshot, F_OK)) {
80             return 0;
81         }

2.4 启动模式

Systemd是按照目标target来启动的,默认的target是graphical.target,以这个target为目标,systemd会按照依赖关系完成local-fs.target、sysinit.target、basic.target以及multi-user.target的启动,由于绝大部分系统服务被multi-user或graphical依赖,因此会随着目标target的启动被拉起,具体见下图:
systemd_bootmode

为适应移动设备多启动模式的需要,我们修改了systemd使其可以支持多种启动模式,目前已经支持的模式是charge-only mode。下面以该模式为例介绍如何增加新的启动模式:

一、我们新增加了一个charger-mode.target,该target只依赖basic.target,因此当系统启动到charger-mode时不会拉起系统服务:

[Unit]
Description=Charger Mode
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target

二、在charge-only mode模式需要运行charger service和用户进行交互,因此配置charger.service,并安装到charger-mode.target.wants,使其依赖charger-mode.target,这样当系统启动到charger-mode.target时就可以自动拉起charger service,charger.service的配置如下:

[Unit]
Description=Charge Service

[Service]
ExecStart=/usr/bin/charge
Restart=always
NonBlocking=true

[Install]
WantedBy=charger-mode.target

三、配置完charger-mode.target和charger.service后还需要修改systemd代码才可以使systemd在从/proc/cmdline读取到特定字符串后以charger-mode.target为目标进行启动,这部分代码位于/src/core/main.c的parse_proc_cmdline_item函数内:

271 static int set_yunos_boot_mode(const char *u) {
272         assert(u);
273 
274         if (streq(u, "charger")) {
275             //charger mode
276             return set_default_unit("charger-mode.target");
277         }
278 
279         return 0;
280 }
...
313         } else if (streq(key, "androidboot.mode") && value) {
314                 if (!in_initrd())
315                         return set_yunos_boot_mode(value);

2.5 加载内核模块

Systemd支持启动时加载内核模块,有两种方式:1. 利用systemd-modules-load服务读取配置文件或kernel cmdline加载;2. 读取/src/core/kmod-setup.c的kmod_table加载,建议两种方式选其一。

2.5.1 systemd-module-load

Systemd-module-load是systemd启动时专门用来加载内核模块的服务,首先需要使能这个服务:

  1. 修改systemd代码库/yunos-mk/ yunos-systemd-config-prebuilt.mk的LOCAL_COMPILE_SHELL_CMD的sysinit.target.wants里增加符号链接:
    $(LN_S) ../systemd-modules-load.service systemd-modules-load.service && \
  2. 删除这行:
    $(RM_F) $(YUNOS_ROOT_ORIGIN)/$(XMAKE_ROOTFS)/usr/lib$(lib_suffix)/systemd/system/systemd-modules-load.service && \

这样编译出的系统systemd可以使用systemd-module-load加载内核模块,该服务读取位于/etc/modules-load.d/*.conf或/usr/lib/modules-load.d/*.conf里的配置文件或直接从kernel cmdline字段modules_load=获取需要加载的内核模块名称,.conf配置文件可以按需定制并在编译时安装到/etc/modules-load.d/或/usr/lib/modules-load.d/,例如:

/etc/modules-load.d/virtio-net.conf example:

# Load virtio-net.ko at boot
virtio-net

2.5.2 kmod_table

Systemd启动时会执行kmod_setup函数,在这个函数内systemd读取kmod_table里的信息并加载内核模块,可以直接修改/src/core/kmod-setup.c内的kmod_table实现系统启动加载需要的内核模块:

56         static const struct {
57                 const char *module;
58                 const char *path;
59                 bool warn;
60                 bool (*condition_fn)(void);
61         } kmod_table[] = {
62                 /* auto-loading on use doesn't work before udev is up
63                 { "autofs4", "/sys/class/misc/autofs", true, NULL                 },*/
64 
65                 /* early configure of ::1 on the loopback device */
66                 { "ipv6",    "/sys/module/ipv6",       true, NULL                 },
67 
68                 /* this should never be a module */
69                 { "unix",    "/proc/net/unix",         true, NULL                 },
70 
71                 /* IPC is needed before we bring up any other services */
72                 { "kdbus",   "/sys/fs/kdbus",          false, cmdline_check_kdbus },
73         };

2.6 系统服务配置

Systemd的服务都有一个.service的单元文件,文件一般由三部分组成,分别是[Unit]、[Service]和[Install]。
例如systemservice.service:

[Unit]
Description=YUNOS System Service
After=pagemanagerd.service

[Service]
Type=notify
ExecStart=/usr/bin/systemserviced
Restart=always
NonBlocking=true
UMask=0000
SupplementaryGroups=inet
User=system
Group=system
SecureBits=keep-caps
Capabilities=cap_sys_time=ep
CapabilityBoundingSet=CAP_SYS_TIME

[Install]
WantedBy=graphical.target

2.6.1 [Unit]段

[Unit]段是不依赖特定文件类型的一些一般信息,例如:
Description=
服务描述,一般是关于服务的说明。
Requires=
指定此服务依赖的其它服务,如果本服务被激活,那么 Requires 后面的服务也会被激活,反之,如果 Requires 后面的服务被停止或无法启动,则本服务也会停止。
Conflicts=
依赖冲突,如果配置了该项,当一个服务启动时会停止此处列出的服务,反过来,如果这里列出的服务启动,那么该服务会停止,即后启动的起作用。
Before=, After=
配置服务间的启动顺序,例如一个 foo.service 包含了一行 Before=bar.service,那么当他们同时启动时,bar.service 会等待 foo.service 启动完成后才启动。
DefaultDependencies=
布尔值,如果是True(默认值),一些服务默认的依赖会隐式的建立,对于普通的服务(.service类型),它会确保在系统基本服务启动后才启动服务,会在系统关机前确保服务已关闭。一般来说,只有早期开机服务和后期的关机服务,才需要把这个设成False。

2.6.2 [Service]段

[Service]段描述了服务的基本信息和运行变量,例如:
Type=
设置进程的启动类型,必须是下列类型之一:simple(默认值), forking, oneshot, dbus, notify, idle,需要注意的是notify类型,该进程将会在启动完成之后通过 sd_notify() 接口发送一个通知消息,systemd 会在启动后继单元之前,首先确保该进程已经成功的发送了这个消息。
RemainAfterExit=
可设为yes或no(默认值),表示当该服务的所有进程全部退出之后,是否依然将此服务视为活动状态,一般与Type=oneshot类型配合使用。
ExecStart=
在启动该服务时需要执行的命令行(命令+参数)。
ExecStartPre=, ExecStartPost=
设置在执行 ExecStart= 之前/后执行的命令行,语法规则与 ExecStart= 完全相同。
ExecStop=
这是一个可选的指令,用于设置当该服务被要求停止时所执行的命令行。语法规则与 ExecStart= 完全相同。
WatchdogSec=
设置该服务的看门狗的超时时长。看门狗将在服务成功启动之后被激活。该服务在运行过程中必须周期性的以WATCHDOG=1调用 sd_notify() 函数。
Restart=
当服务进程正常退出、异常退出、被杀死、超时的时候,是否重新启动该服务。该选项可以取下列值之一:no(默认值), on-success, on-failure, on-abnormal, on-watchdog, on-abort, always。当进程是由于 systemd 的正常操作(例如 systemctl stop/restart)而被停止时,该服务不会被重新启动。
Sockets=
设置一个socket单元的名称,表示该服务在启动时应当从它继承socket文件描述符。通常并不需要明确设置此选项,因为所有与该服务同名的socket单元都会被自动的传递给派生进程。
StartLimitInterval=, StartLimitBurst=
限制该服务的启动频率。默认值是每10秒内不得超过5次(StartLimitInterval=10s StartLimitBurst=5)。
RootDirectory=
设置服务的根目录,效果和chroot()系统调用类似。
User=, Group=
设置服务运行时的UID和GID,可以是用户名、组名或数字,默认值是root。
SupplementaryGroups=
可设置服务运行需要加入的组,由一列组名或GID组成。
OOMScoreAdjust=
设置内存不足时杀进程的优先级,取值从-1000(关闭OOM Killing)到1000(内存不足时优先杀掉)。
UMask=
控制创建文件时的mask位,默认值是0022。
Environment=
设置服务运行时的环境变量,例如,Environment=“VAR1=word1 word2” VAR2=word3 “VAR3=$word 5 6”设置了三个环境变量VAR1、VAR2、VAR3。
SecureBits=
设置进程的secure bits,可取值keep-caps, keep-caps-locked, no-setuid-fixup, no-setuid-fixup-locked, noroot, noroot-locked。若要设置服务的Capability,需要置为keep-caps。
CapabilityBoundingSet=
控制服务启动后进程的Capability Bounding Set,例如CapabilityBoundingSet=CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SETPCAP设置服务进程的Bounding Set为以上三个Capability。
Capabilities=
设置服务进程运行时的Capability,他必须是CapabilityBoundingSet的一个子集,例如Capabilities=cap_sys_nice=ep cap_sys_resource=ep设置服务进程的effective 和 permitted capability为cap_sys_nice和cap_sys_resource。备注:该功能依赖于YunOS的inheritCap特性。

2.6.3 [Install]段

[Install]是个可选配置选项,它描述了服务的安装信息。它不在 systemd 的运行期间使用。只在使用 systemctl enable 和 systemctl disable 命令启用/禁用服务时有用。例如:
Alias=
在安装使用应该使用的额外名字(即别名)。名字必须和服务本身有同样的后缀。这个选项可以指定多次,所有的名字都起作用,当执行 systemctl enable 命令时,会建立相应的符号链接。
WantedBy=, RequiredBy=
在 .wants/ 或 .requires/ 子目录中为服务建立相应的链接。这样做的效果是当列表中的服务启动,该服务也会启动。
Also=
当此服务安装时同时需要安装的附加服务。 如果用户请求安装的服务中配置了此项,则 systemctl enable 命令执行时会自动安装本项所指定的服务。

2.6.4 服务添加

2.6.4.1 代码库中添加新的systemd服务

Systemd代码位于codebase_host/third_party/systemd/
添加步骤:
1. 参考以上2.6节的描述写好foo.service服务单元文件;
2. 将新的单元文件添加到代码库/units/yunos/[phone, yunhal, tablet, tv or ivi]/内;
3. 修改systemd的编译文件,找到对应产品线的ifeq ($(SYSTEMD_HAVE_YUNOSXXX),true)
4. 在dist_etcsystemunit_DATA += \下按照格式添加units/yunos/XXX/foo.service;
5. 在LOCAL_COMPILE_SHELL_CMD := \下找到服务所在target(一般是加到multi-user.target)下添加$(LN_S) ../foo.service foo.service && \

这样在编译打包后生成的image中,YunOS搭载的systemd服务将被安装到/etc/systemd/system/目录下,并且系统启动时会自动启动该服务,如果不想做成开机启动,可省略步骤5。

2.6.4.2 本地添加systemd服务调试

添加步骤:
1. 参照2.6节完成systemd服务的foo.service单元文件编写;
2. adb -host push服务的单元文件到机器host侧/etc/systemd/system/目录下;
3. adb -host shell cd到/etc/systemd/system/multi-user.target.wants/,建立一个指向上级目录对应单元文件的符号链接,例如 ln –s ../foo.service foo.service
4. 重启机器,服务会开机启动,利用systemctl status foo.service命令查看服务启动状况

如果不需要服务开机启动,可省略步骤3,通过systemctl start foo.service命令手动启动。

3 调试手段

Systemd的debug主要依靠日志系统以及systemd提供的systemd-bootchart、journalctl、systemctl工具,必要时也可以使用gdb工具查看堆栈。

3.1 日志

Systemd提供了非常方便的日志系统(systemd-journald),通常利用它就可以很好的定位问题了,利用它查看系统早期启动时的关键日志信息定位问题,例如分析服务启动失败或文件挂载失败等。另外也可以利用它分析关机流程,定位Bug。
Systemd的日志默认是输出到journald,在系统启动早期journald还没准备好时可能存在日志流失的问题,如果有串口设备,可以利用串口设备查看这些日志,如果没有串口设备,则需要修改systemd的配置实现将log导入kmsg:

adb -host pull /etc/systemd/system.conf
打开 system.conf
修改 LogLevel=debug LogTarget=kmsg
adb -host push system.conf /etc/systemd/

如上所述,重启机器后systemd的debug日志就会导入到kmsg中,只需要 执行cat /dev/kmsg | grep systemd命令就能拿到systemd的日志信息了。

3.2 Systemd-bootchart工具

Systemd-bootchart提供了图形化的、详细的系统启动统计信息,用于分析系统启动的performance,当然也可以用它来进行某些系统启动问题的debug。

3.2.1 systemd-bootchart使能

修改host侧的编译脚本mk-image.sh,将kernel cmdline改为:

init=/usr/lib/systemd/systemd-bootchart

另外,需要注意systemd-bootchart统计系统启动信息需要访问/proc文件系统的debug接口,所以要在内核配置中打开CONFIG_SCHEDSTATS和CONFIG_SCHED_DEBUG:

CONFIG_SCHED_DEBUG=y
CONFIG_SCHEDSTATS=y

3.2.2 bootchart文件

系统完成开机启动后,会生成一份可视svg文件到/dev/目录下,命名为bootchart-xxx.svg,例如bootchart-20120106-0128.svg,这份文件记录了本次开机启动过程中系统IO和CPU的占用情况,并用不同的颜色标注,可以手动adb pull出来查看和分析,更多的介绍请参阅:http://man7.org/linux/man-pages/man1/systemd-bootchart.1.html

3.3 Journalctl工具

Journalctl工具是systemd自带的日志系统工具,对于systemd以及系统服务进程的调试比较有用,介绍一些常用命令。
语法:

journalctl [OPTIONS...] [MATCHES...]

像logctl/logcat一样持续查看journald日志

journalctl -f

只关心某个服务(例如foo.service)的日志

journalctl _SYSTEMD_UNIT=foo.service

如果还想利用journalctl查询更多的信息,请参考:https://www.freedesktop.org/software/systemd/man/journalctl.html#

注意,journalctl能查看的log类型需要修改/etc/systemd/journald.conf配置文件的MaxLevelStore字段进行配置,为减少CPU占用,目前默认配置为只显示err以上的log,若需要显示全部log,请修改MaxLevelStore=debug

3.4 Systemctl工具

Systemctl是systemd提供的一个功能非常强大的管理工具,它可以用来启动/停止服务、分析服务依赖关系、查询服务状态、开机/关机等,对于服务的调试是非常有用的。例如,启动服务失败时,systemctl会给出服务启动的错误信息:

# systemctl start foo.service
Job failed. See system journal and 'systemctl status' for details.

接着,可以继续利用systemctl查看失败原因:

# systemctl status foo.service
foo.service - mmm service
          Loaded: loaded (/etc/systemd/system/foo.service; static)
          Active: failed (Result: exit-code) since Fri, 11 May 2012 20:26:23 +0200; 4s ago
         Process: 1329 ExecStart=/usr/local/bin/foo (code=exited, status=1/FAILURE)
          CGroup: name=systemd:/system/foo.service

May 11 20:26:23 scratch foo[1329]: Failed to parse config

在上面这个例子中我们可以看到foo.service这个服务以PID 1329启动,在parse config时出错,错误退出码是1。

Systemctl语法:

systemctl [OPTIONS...] COMMAND [NAME...]

查询某个服务的状态

systemctl status foo.service

手动启动/重启/停止某个服务

systemctl start foo.service
systemctl restart foo.service
systemctl stop foo.service

显示系统中所有的服务,包括已启动的和未启动的

systemctl --all list-units

查询服务依赖

systemctl list-dependencies foo.service           [foo require和want的服务]
systemctl --after list-dependencies foo.service   [在foo之前启动的服务]
systemctl --before list-dependencies foo.service  [在foo之后启动的服务]

当然,systemctl工具还提供了很多其他功能,请参考:https://www.freedesktop.org/software/systemd/man/systemctl.html

3.5 Gdb工具

Gdb工具可以attach到systemd查看systemd的堆栈情况,例如

# gdb attach 1
...
...
...
(gdb) bt
bt
#0  0xad591c6c in epoll_wait () from /usr/lib/libc.so.6
#1  0xad816512 in sd_event_wait () from /usr/lib/libkdbus.so
#2  0xad816cce in sd_event_run () from /usr/lib/libkdbus.so
#3  0x0003e6f6 in ?? ()
#4  0x0001ea12 in ?? ()
#5  0xad4d3794 in __libc_start_main () from /usr/lib/libc.so.6
Cannot access memory at address 0x0
#6  0x0002073c in ?? ()
Cannot access memory at address 0x0
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

4 附录

4.1 参考

systemd
systemd-bootchart
journalctl
systemctl
systemd.mount
systemd.special
bootup
modules-load
udev
cn-udev
writing_udev_rules

FAQ

关于此文档暂时还没有FAQ
返回
顶部