文档内容拷贝自:
codebase_host/yunhal/opendoc/zh/porting_guide/systemd_and_init.md
联系人:赤铁
版本:commit-id(93385c3c9f53d946600ad2611e68dc1207761889
)
版本编号 | 修订内容 | 日期 | 修订人 |
---|---|---|---|
0.1 | 初稿 | 2017.07.05 | 李卿 |
6.0 | 更新稿 | 2017.08.16 | 李卿 |
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上系统启动速度大大加快。
Systemd作为系统首个启动的进程,使用udevd配置设备节点,利用.mount单元文件配置分区挂载、通过解析.service单元文件启动系统服务,这些工作分成不同的步骤,并暴露成target单元。Systemd启动后根据本次启动的taget(可通过kernel cmdline指定)为各个任务建立依赖关系并完成YunOS系统的初始化,在Android Runtime Plugin安装后会由systemd启动的服务pluginmanagerd完成后续整个Android系统的启动,启动过程如下图所示:
版本[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
Kernel从init切换启动systemd,需要修改boot.image的kernel cmdline:
init=/usr/lib/systemd/systemd
Systemd使用udev管理设备。当设备添加/删除时,udevd监听来自内核的uevent,添加或者删除/dev下的设备文件,并且 通过定义一个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 && \
在sysinit.target.wants里增加符号链接:
$(LN_S) ../systemd-udev-trigger.service systemd-udev-trigger.service && \ $(LN_S) ../systemd-udevd.service systemd-udevd.service && \
删除以下几行:
$(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规则。
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
YunOS系统的分区一般有system分区、data分区、cache分区以及plugin分区组成。由于Host系统不带有ramdisk.img,因此system分区的挂载由kernel cmdline指定root=/dev/mmcblk0p7
(设备不同名称可能不同) 完成;data分区和cache分区可以利用systemd提供的.mount单元文件进行配置,或直接修改systemd的mount table;plugin.img则是由服务pluginmanager挂载。
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的进程。
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
挂载选项,不同的文件系统可能支持不同的选项
完成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 && \
本地调试验证时可直接将单元文件push到/etc/systemd/system/然后去/etc/systemd/system/local-fs.target.wants/目录建立指向上级目录/etc/systemd/system/里单元文件的符号链接,重启系统即可生效。
注意到mount单元文件一般是用来支持块设备分区的挂载,如果需要挂载的设备不是块设备,可直接修改systemd的mount table实现系统启动时分区的挂载。
具体来说,systemd的mount_table位于systemd代码/src/core/mount-setup.c的static const MountPoint mount_table[]
,在mount_table里添加需要挂载的信息即可,这种修改方式建议使用宏定义区分平台和产品。
需要注意,不要在配置mount单元文件的同时还修改mount_table,建议这两种方式选其一。
可读写分区挂载完成后,需要利用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 }
Systemd是按照目标target来启动的,默认的target是graphical.target,以这个target为目标,systemd会按照依赖关系完成local-fs.target、sysinit.target、basic.target以及multi-user.target的启动,由于绝大部分系统服务被multi-user或graphical依赖,因此会随着目标target的启动被拉起,具体见下图:
为适应移动设备多启动模式的需要,我们修改了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);
Systemd支持启动时加载内核模块,有两种方式:1. 利用systemd-modules-load服务读取配置文件或kernel cmdline加载;2. 读取/src/core/kmod-setup.c的kmod_table加载,建议两种方式选其一。
Systemd-module-load是systemd启动时专门用来加载内核模块的服务,首先需要使能这个服务:
$(LN_S) ../systemd-modules-load.service systemd-modules-load.service && \
$(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
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 };
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
[Unit]段是不依赖特定文件类型的一些一般信息,例如:
Description=
服务描述,一般是关于服务的说明。
Requires=
指定此服务依赖的其它服务,如果本服务被激活,那么 Requires 后面的服务也会被激活,反之,如果 Requires 后面的服务被停止或无法启动,则本服务也会停止。
Conflicts=
依赖冲突,如果配置了该项,当一个服务启动时会停止此处列出的服务,反过来,如果这里列出的服务启动,那么该服务会停止,即后启动的起作用。
Before=, After=
配置服务间的启动顺序,例如一个 foo.service 包含了一行 Before=bar.service,那么当他们同时启动时,bar.service 会等待 foo.service 启动完成后才启动。
DefaultDependencies=
布尔值,如果是True(默认值),一些服务默认的依赖会隐式的建立,对于普通的服务(.service类型),它会确保在系统基本服务启动后才启动服务,会在系统关机前确保服务已关闭。一般来说,只有早期开机服务和后期的关机服务,才需要把这个设成False。
[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特性。
[Install]是个可选配置选项,它描述了服务的安装信息。它不在 systemd 的运行期间使用。只在使用 systemctl enable 和 systemctl disable 命令启用/禁用服务时有用。例如:
Alias=
在安装使用应该使用的额外名字(即别名)。名字必须和服务本身有同样的后缀。这个选项可以指定多次,所有的名字都起作用,当执行 systemctl enable 命令时,会建立相应的符号链接。
WantedBy=, RequiredBy=
在 .wants/ 或 .requires/ 子目录中为服务建立相应的链接。这样做的效果是当列表中的服务启动,该服务也会启动。
Also=
当此服务安装时同时需要安装的附加服务。 如果用户请求安装的服务中配置了此项,则 systemctl enable 命令执行时会自动安装本项所指定的服务。
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。
添加步骤:
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
命令手动启动。
Systemd的debug主要依靠日志系统以及systemd提供的systemd-bootchart、journalctl、systemctl工具,必要时也可以使用gdb工具查看堆栈。
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的日志信息了。
Systemd-bootchart提供了图形化的、详细的系统启动统计信息,用于分析系统启动的performance,当然也可以用它来进行某些系统启动问题的debug。
修改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
系统完成开机启动后,会生成一份可视svg文件到/dev/目录下,命名为bootchart-xxx.svg,例如bootchart-20120106-0128.svg,这份文件记录了本次开机启动过程中系统IO和CPU的占用情况,并用不同的颜色标注,可以手动adb pull出来查看和分析,更多的介绍请参阅:http://man7.org/linux/man-pages/man1/systemd-bootchart.1.html
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
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
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?)
systemd
systemd-bootchart
journalctl
systemctl
systemd.mount
systemd.special
bootup
modules-load
udev
cn-udev
writing_udev_rules