Skip to content

模块开发指南 ​

ShizuSU 提供了一个模块机制,它可以在保持系统分区完整性的同时达到修改系统分区的效果;这种机制通常被称之为 systemless。ShizuSU 的模块系统基于 Magic Mount(源自 SukiSU-Ultra),无需安装 metamodule,模块开箱即用。

ShizuSU 的模块运作机制与 Magisk 几乎是一样的,如果你熟悉 Magisk 模块的开发,那么开发 ShizuSU 的模块大同小异,你可以跳过下面有关模块的介绍,只需要了解 ShizuSU 模块与 Magisk 模块的异同。

模块系统基于 Magic Mount

ShizuSU 的模块系统基于 Magic Mount(来自 SukiSU-Ultra),无需安装 metamodule 即可挂载 system 目录。修改 /system 文件的模块开箱即用。详见模块系统:Magic Mount。

KPM 内核模块 ​

KernelPatch Module(KPM)允许直接在内核层面加载模块,适合进行高级内核修改与增强。ShizuSU 完整支持 KPM(移植自 Apatch)。使用 KPM 需要在内核编译时开启 CONFIG_KPM=y。

模块界面 ​

ShizuSU 的模块支持显示界面并与用户交互,请参阅 WebUI 文档。

模块配置 ​

ShizuSU 提供了一个内置的配置系统,允许模块存储持久化或临时的键值设置。详情请参阅模块配置文档。

Busybox ​

ShizuSU 提供了一个功能完备的 BusyBox 二进制文件(包括完整的 SELinux 支持)。可执行文件位于 /data/adb/ksu/bin/busybox。 ShizuSU 的 BusyBox 支持运行时可切换的 "ASH Standalone Shell Mode"。 这种独立模式意味着在运行 BusyBox 的 ash shell 时,每个命令都会直接使用 BusyBox 中内置的应用程序,而不管 PATH 设置为什么。 例如,ls、rm、chmod 等命令将不会使用 PATH 中设置的命令(在 Android 的情况下,默认情况下分别为 /system/bin/ls、/system/bin/rm 和 /system/bin/chmod),而是直接调用 BusyBox 内置的应用程序。 这确保了脚本始终在可预测的环境中运行,并始终具有完整的命令套件,无论它运行在哪个 Android 版本上。 要强制一个命令不使用 BusyBox,你必须使用完整路径调用可执行文件。

在 ShizuSU 上下文中运行的每个 shell 脚本都将在 BusyBox 的 ash shell 中以独立模式运行。对于第三方开发者相关的内容,包括所有启动脚本和模块安装脚本。

对于想要在 ShizuSU 之外使用这个“独立模式”功能的用户,有两种启用方法:

  1. 设置环境变量 ASH_STANDALONE 为 1。例如:ASH_STANDALONE=1 /data/adb/ksu/bin/busybox sh <script>
  2. 使用命令行选项切换:/data/adb/ksu/bin/busybox sh -o standalone <script>

为了确保所有后续的 sh shell 都在独立模式下执行,第一种是首选方法(这也是 ShizuSU 和 ShizuSU 管理器内部使用的方法),因为环境变量会被继承到子进程中。

与 Magisk 的差异

ShizuSU 的 BusyBox 现在是直接使用 Magisk 项目编译的二进制文件,感谢 Magisk! 因此,你完全不用担心 BusyBox 脚本与在 Magisk 和 ShizuSU 之间的兼容问题,因为他们是完全一样的!

ShizuSU 模块 ​

ShizuSU 模块就是一个放置在 /data/adb/modules 内且满足如下结构的文件夹:

txt
/data/adb/modules
├── .
├── .
|
├── $MODID                  <--- 模块的文件夹名称与模块 ID 相同
│   │
│   │      *** 模块配置文件 ***
│   │
│   ├── module.prop         <--- 此文件保存模块相关的一些配置,如模块 ID、版本等
│   │
│   │      *** 模块内容 ***
│   │
│   ├── system              <--- 这个文件夹通常会被挂载到系统
│   │   ├── ...
│   │   ├── ...
│   │   └── ...
│   │
│   │      *** 标记文件 ***
│   │
│   ├── skip_mount          <--- 如果这个文件存在,那么模块的 `/system` 将不会被挂载
│   ├── disable             <--- 如果这个文件存在,那么模块会被禁用
│   ├── remove              <--- 如果这个文件存在,下次重启的时候模块会被移除
│   │
│   │      *** 可选文件 ***
│   │
│   ├── post-fs-data.sh     <--- 这个脚本将会在 post-fs-data 模式下运行
│   ├── post-mount.sh       <--- 这个脚本将会在 post-mount 模式下运行
│   ├── service.sh          <--- 这个脚本将会在 late_start 服务模式下运行
│   ├── boot-completed.sh   <--- 这个脚本将会在 Android 系统启动完毕后以服务模式运行
|   ├── uninstall.sh        <--- 这个脚本将会在模块被卸载时运行
│   ├── system.prop         <--- 这个文件中指定的属性将会在系统启动时通过 resetprop 更改
│   ├── sepolicy.rule       <--- 这个文件中的 SELinux 策略将会在系统启动时加载
│   ├── initrc/             <--- 此目录下的 .rc 文件将在启动时注入 init.rc
│   │   ├── myservice.rc
│   │   └── ...
│   │
│   │      *** 自动生成的目录,不要手动创建或者修改! ***
│   │
│   ├── vendor              <--- 如果 /system/vendor 是符号链接且存在,从 $MODID/system/vendor 移动到模块根目录
│   ├── product             <--- 如果 /system/product 是符号链接且存在,从 $MODID/system/product 移动到模块根目录
│   ├── system_ext          <--- 如果 /system/system_ext 是符号链接且存在,从 $MODID/system/system_ext 移动到模块根目录
│   │
│   │      *** Any additional files / folders are allowed ***
│   │
│   ├── ...
│   └── ...
|
├── another_module
│   ├── .
│   └── .
├── .
├── .

与 Magisk 的差异

ShizuSU 没有内置的针对 Zygisk 的支持,因此模块中没有 Zygisk 相关的内容,但你可以通过 ZygiskNext 来支持 Zygisk 模块,此时 Zygisk 模块的内容与 Magisk 所支持的 Zygisk 是完全相同的。

module.prop ​

module.prop 是一个模块的配置文件,在 ShizuSU 中如果模块中不包含此文件,那么它将不被认为是一个模块;此文件的格式如下:

txt
id=<string>
name=<string>
version=<string>
versionCode=<int>
author=<string>
description=<string>
updateJson=<url> (optional)
actionIcon=<path> (optional)
webuiIcon=<path> (optional)
  • id 必须与这个正则表达式匹配:^[a-zA-Z][a-zA-Z0-9._-]+$ 例如:✓ a_module,✓ a.module,✓ module-101,✗ a module,✗ 1_module,✗ -a-module。这是您的模块的唯一标识符,发布后不应更改。
  • versionCode 必须是一个整数,用于比较版本。
  • 其他未在上面提到的内容可以是任何单行字符串。
  • 请确保使用 UNIX(LF)换行类型,而不是 Windows(CR + LF)或 Macintosh(CR)。
  • actionIcon 和 webuiIcon 是可选的图标路径,用作管理器中模块 Action 快捷方式和 WebUI 快捷方式的默认图标。这些路径必须是基于模 块根目录的相对路径。例如 actionIcon=icon/icon.png 将会解析为 <MODDIR>/icon/icon.png。

动态描述

description 字段可以在运行时使用模块配置系统动态覆盖。详情请参阅覆盖模块描述。

Shell 脚本 ​

请阅读 启动脚本 一节,以了解 post-fs-data.sh, post-mount.sh, service.sh 和 boot-completed.sh 之间的区别。对于大多数模块开发者来说,如果您只需要运行一个启动脚本,service.sh 应该已经足够了。

在您的模块的所有脚本中,请使用MODDIR=${0%/*}来获取您的模块的基本目录路径;请勿在脚本中硬编码您的模块路径。

与 Magisk 的差异

你可以通过环境变量 KSU 来判断脚本是运行在 ShizuSU 还是 Magisk 中,如果运行在 ShizuSU,这个值会被设置为 true。

system 目录 ​

这个目录的内容会在系统启动后,以 Magic Mount(bind mount) 的方式叠加在系统的 /system 分区之上,这意味着:

  1. 系统中对应目录的同名文件会被此目录的文件覆盖。
  2. 系统中对应目录的同名文件夹会与此目录的文件夹合并。

如果你想删掉系统原来目录某个文件或者文件夹,你可以在 customize.sh 中声明一个名为 REMOVE 并且包含一系列目录的变量来执行删除操作,ShizuSU 会通过 Magic Mount 自动隐藏这些文件(/system 分区并没有被更改)。例如:

sh
REMOVE="
/system/app/YouTube
/system/app/Bloatware
"

上面的这个列表将会在模块生效后隐藏 /system/app/YouTube 和 /system/app/Bloatware。

如果你想替换掉系统的某个目录,你可以在 customize.sh 中声明一个名为 REPLACE 并且包含一系列目录的变量来执行替换操作,ShizuSU 会把模块目录中对应路径挂载为空目录,实现整目录替换。例如:

sh
REPLACE="
/system/app/YouTube
/system/app/Bloatware
"

上面这个列表将会在模块生效后把 /system/app/YouTube 和 /system/app/Bloatware 替换为空目录。

与官方 KernelSU 的差异

官方 KernelSU 使用 OverlayFS(metamodule)机制实现 systemless,而 ShizuSU 基于 SukiSU-Ultra,与 Magisk 一样使用 Magic Mount(bind mount) 实现。两种方案的目标一致:不修改物理的 /system 分区但实现修改 /system 文件。ShizuSU 只使用 Magic Mount,不存在两种模块系统。详见模块系统:Magic Mount。

system.prop ​

这个文件的格式与 build.prop 完全相同:每一行都是 [key]=[value] 的形式。

sepolicy.rule ​

如果您的模块需要一些额外的 SELinux 策略补丁,请将这些规则添加到此文件中。这个文件中的每一行都将被视为一个策略语句。

initrc 注入 ​

ShizuSU 提供了一种将自定义 Android Init RC 指令注入系统 init.rc 的机制。这使得模块可以在不修改系统分区的情况下注册自定义的 Android 服务、设置属性触发器或执行其他 Init 语言操作。

在启动过程中,ShizuSU 的内核模块通过 hook read() 和 fstat() 系统调用,在 Android init 进程读取 /system/etc/init/hw/init.rc 时,将自定义 RC 内容透明地追加到文件末尾。init 进程会像处理原始 init.rc 内容一样解析这些注入的指令。

在用户空间侧,ksud 会将所有已启用模块的 .rc 文件合并到一个 modules.rc 文件中,存放在 /metadata 分区上。每当模块状态发生变化(安装、启用、禁用、卸载等)时,这个文件都会被自动重新生成。

模块 initrc 文件 ​

在模块目录中创建一个 initrc/ 子目录,将 .rc 文件放置其中:

txt
/data/adb/modules/<MODID>/
├── initrc/
│   ├── myservice.rc
│   └── another.rc
└── ...

TIP

  • 文件必须以 .rc 为扩展名。
  • 只要模块处于启用状态,initrc/ 目录下的所有 .rc 文件都会被包含(无需设置可执行权限)。
  • 处理的先后顺序为:同一目录内的文件按文件名字母顺序排列;模块之间按模块 ID 字母顺序排列。

通用 initrc 文件 ​

除了模块级别的 RC 文件外,你还可以将 .rc 文件放置在全局目录中:

txt
/data/adb/initrc.d/
├── myservice.rc
└── another.rc

全局 initrc 文件需要可执行权限

与模块 initrc/ 目录不同,/data/adb/initrc.d/ 中的文件必须设置可执行权限才会被包含。不具有可执行权限的 .rc 文件会被静默跳过。

全局 initrc.d/ 文件在所有模块 RC 文件之前被处理。

示例 ​

以下是一个注册自定义 Android 服务的 .rc 文件示例:

rc
service myservice /data/adb/modules/mymodule/bin/myservice
    user root
    group root
    disabled
    seclabel u:r:ksu:s0

on property:sys.boot_completed=1
    start myservice

此文件放置在 /data/adb/modules/mymodule/initrc/myservice.rc,将在系统启动时注册一个名为 myservice 的服务,并在 sys.boot_completed=1 时启动它。

手动刷新 ​

你可以通过以下命令手动触发 modules.rc 重新生成(更改将在下次启动时生效):

sh
ksud initrc refresh

TIP

  • initrc 注入发生在启动极早期(init 读取 init.rc 时),早于 post-fs-data 和所有模块脚本的执行。
  • 注入的 RC 内容被 init 当作原始 init.rc 的一部分处理,支持所有 Android Init 语言语法(服务定义、触发器、属性设置等)。
  • 在 late-load 模式下,initrc 注入不可用,因为系统调用 hook 在该模式下不会被安装。
  • 可以在 ksud 修补镜像的时候传入参数--no-custom-rc 禁用模块 RC 注入。

模块安装包 ​

ShizuSU 的模块安装包就是一个可以通过 ShizuSU 管理器 APP 刷入的 zip 文件,此 zip 文件的格式如下:

txt
module.zip
│
├── customize.sh                       <--- (Optional, more details later)
│                                           This script will be sourced by update-binary
├── ...
├── ...  /* 其他模块文件 */
│

WARNING

ShizuSU 模块不支持在 Recovery 中安装!!

定制安装过程 ​

如果你想控制模块的安装过程,可以在模块的目录下创建一个名为 customize.sh 的文件,这个脚本将会在模块被解压后导入到当前 shell 中,如果你的模块需要根据设备的 API 版本或者设备构架做一些额外的操作,那这个脚本将非常有用。

如果你想完全控制脚本的安装过程,你可以在 customize.sh 中声明 SKIPUNZIP=1 来跳过所有的默认安装步骤;此时,你需要自行处理所有安装过程(如解压模块,设置权限等)

customize.sh 脚本以“独立模式”运行在 ShizuSU 的 BusyBox ash shell 中。你可以使用如下变量和函数:

变量 ​

  • KSU (bool): 标记此脚本运行在 ShizuSU 环境下,此变量的值将永远为 true,你可以通过它区分 Magisk。
  • KSU_VER (string): ShizuSU 当前的版本名字 (如: v0.4.0)
  • KSU_VER_CODE (int): ShizuSU 用户空间当前的版本号 (如. 10672)
  • KSU_KERNEL_VER_CODE (int): ShizuSU 内核空间当前的版本号 (如. 10672)
  • BOOTMODE (bool): 此变量在 ShizuSU 中永远为 true
  • MODPATH (path): 当前模块的安装目录
  • TMPDIR (path): 可以存放临时文件的目录
  • ZIPFILE (path): 当前模块的安装包文件
  • ARCH (string): 设备的 CPU 构架,有如下几种: arm, arm64, x86, or x64
  • IS64BIT (bool): 是否是 64 位设备
  • API (int): 当前设备的 Android API 版本 (如:Android 6.0 上为 23)
  • KSU_UAPI_VER (int): ShizuSU 用户空间 (ksud) 的 UAPI 版本号 (如:2)。当内核驱动发生破坏性更改时此版本号会递增,模块可据此判断兼容性。
  • KSU_RUNTIME_MODE (string): ShizuSU 当前的运行模式。可能的值为 built-in(即 GKI 模式,编译进内核)、lkm(开机时作为内核模块加载)或 late-load(开机后作为内核模块加载)。
  • KSU_LATE_LOAD (int?): 如果 ShizuSU 是在开机后延迟加载的,此变量的值为 1,否则不设置此变量。

WARNING

MAGISK_VER_CODE 在 ShizuSU 中永远为 25200,MAGISK_VER 则为 v25.2,请不要通过这两个变量来判断是否是 ShizuSU!

函数 ​

txt
ui_print <msg>
    print <msg> to console
    Avoid using 'echo' as it will not display in custom recovery's console

abort <msg>
    print error message <msg> to console and terminate the installation
    Avoid using 'exit' as it will skip the termination cleanup steps

set_perm <target> <owner> <group> <permission> [context]
    if [context] is not set, the default is "u:object_r:system_file:s0"
    this function is a shorthand for the following commands:
       chown owner.group target
       chmod permission target
       chcon context target

set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission> [context]
    if [context] is not set, the default is "u:object_r:system_file:s0"
    for all files in <directory>, it will call:
       set_perm file owner group filepermission context
    for all directories in <directory> (including itself), it will call:
       set_perm dir owner group dirpermission context

启动脚本 ​

在 ShizuSU 中,根据脚本运行模式的不同分为两种:post-fs-data 模式和 late_start 服务模式。

  • post-fs-data 模式

    • 这个阶段是阻塞的。在执行完成之前或者 10 秒钟之后,启动过程会暂停。
    • 脚本在任何模块被挂载之前运行。这使得模块开发者可以在模块被挂载之前动态地调整它们的模块。
    • 这个阶段发生在 Zygote 启动之前。
    • 使用 setprop 会导致启动过程死锁!请使用 resetprop -n <prop_name> <prop_value> 代替。
    • 只有在必要时才在此模式下运行脚本。
  • late_start 服务模式

    • 这个阶段是非阻塞的。你的脚本会与其余的启动过程并行运行。
    • 大多数脚本都建议在这种模式下运行。

在 ShizuSU 中,启动脚本根据存放位置的不同还分为两种:通用脚本和模块脚本。

  • 通用脚本

    • 放置在 /data/adb/post-fs-data.d, /data/adb/post-mount.d, /data/adb/service.d 或 /data/adb/boot-completed.d 中。
    • 只有在脚本被设置为可执行(chmod +x script.sh)时才会被执行。
    • 在 post-fs-data.d 中的脚本以 post-fs-data 模式运行,在 service.d 中的脚本以 late_start 服务模式运行。
    • 模块不应在安装过程中添加通用脚本。
  • 模块脚本

    • 放置在模块自己的文件夹中。
    • 只有当模块被启用时才会执行。
    • post-fs-data.sh 以 post-fs-data 模式运行,post-mount.sh 以 post-mount 模式运行,而 service.sh 则以 late_start 服务模式运行,boot-completed 在 Android 系统启动完毕后以服务模式运行。

所有启动脚本都将在 ShizuSU 的 BusyBox ash shell 中运行,并启用“独立模式”。

启动脚本的流程解疑 ​

以下是 Android 的相关启动流程(部分省略),其中包括了 ShizuSU 的操作(带前导星号),应该能帮助你更好地理解这些启动脚本的用途:

txt
0. Bootloader (nothing on screen)
load patched boot.img
load kernel:
    - GKI mode: GKI kernel with ShizuSU integrated
    - LKM mode: stock kernel
...

1. kernel exec init (oem logo on screen):
    - GKI mode: stock init
    - LKM mode: exec ksuinit, insmod kernelsu.ko, exec stock init
mount /dev, /dev/pts, /proc, /sys, etc.
property-init -> read default props
read init.rc
  *initrc injection: Kernel hook appends ShizuSU core RC and module modules.rc to init.rc
...
early-init -> init -> late_init
early-fs
   start vold
fs
  mount /vendor, /system, /persist, etc.
post-fs-data
  *safe mode check
  *execute general scripts in post-fs-data.d/
  *load sepolicy.rule
  *mount tmpfs
  *execute module scripts post-fs-data.sh
    **(Zygisk)./bin/zygisk-ptrace64 monitor
  *(pre)load system.prop (same as resetprop -n)
  *remount modules /system
  *execute general scripts in post-mount.d/
  *execute module scripts post-mount.sh
zygote-start
load_all_props_action
  *execute resetprop (actual set props for resetprop with -n option)
... -> boot
  class_start core
    start-service logd, console, vold, etc.
  class_start main
    start-service adb, netd (iptables), zygote, etc.

2. kernel2user init (rom animation on screen, start by service bootanim)
*execute general scripts in service.d/
*execute module scripts service.sh
*set props for resetprop without -p option
  **(Zygisk) hook zygote (start zygiskd)
  **(Zygisk) mount zygisksu/module.prop
start system apps (autostart)
...
boot complete (broadcast ACTION_BOOT_COMPLETED event)
*execute general scripts in boot-completed.d/
*execute module scripts boot-completed.sh

3. User operable (lock screen)
input password to decrypt /data/data
*actual set props for resetprop with -p option
start user apps (autostart)

如果你对 Android 的 init 语言感兴趣,推荐阅读文档。

Late-load 模式 ​

除了上述标准启动流程外,ShizuSU 还支持 late-load 模式,用于 LKM(可加载内核模块)场景。在该模式下,ShizuSU 内核模块在系统完全启动后加载,而非在 init 过程中加载。

什么时候触发 late-load? ​

通过运行 ksud late-load 命令触发。该命令会:

  1. 检测当前 KMI 版本,从内嵌资源中加载对应的 kernelsu.ko。
  2. 执行模块初始化(SELinux 规则、白名单、feature 等),这些工作在标准启动中发生在 boot 阶段。

由于系统已经完全运行,某些启动时的机制不可用或不需要。

与标准启动的差异 ​

行为标准启动Late-load 模式
内核模块由 init (PID 1) 加载是否(启动后加载)
initrc 注入(模块 .rc 文件注入 init.rc)是不可用
ksud 的 kprobe 钩子 (execve/read/fstat/input)是跳过
安全模式检测(音量键)是始终禁用
启动日志抓取 (logcat/dmesg)是跳过
Magisk 共存检测是跳过
post-fs-data 事件通知内核是跳过
boot-completed 事件通知内核是初始化时直接设置
post-fs-data.sh / post-fs-data.d/ 脚本是由 late-load 阶段替代
system.prop 加载是是
Magic Mount 模块挂载是是
post-mount.sh / post-mount.d/ 脚本是是
service.sh / service.d/ 脚本是是
boot-completed.sh / boot-completed.d/ 脚本是是
KSU_LATE_LOAD 环境变量未设置设置为 1
内核 info 标志位 0x4未设置已设置

脚本执行顺序 ​

在 late-load 模式下,脚本执行顺序如下:

txt
ksud late-load:
  1. 加载 kernelsu.ko(如果尚未加载)
  2. 释放二进制文件、处理模块更新、加载 SELinux 规则、初始化 feature
  3. 执行 late-load.d/ 通用脚本和模块的 late-load 脚本(阻塞)
  4. 加载 system.prop (resetprop -n)
  5. 执行 Magic Mount 模块挂载
  6. 执行 post-mount.d/ 通用脚本和模块的 post-mount.sh(阻塞)
  7. 执行 service.d/ 通用脚本和模块的 service.sh(非阻塞)
  8. 执行 boot-completed.d/ 通用脚本和模块的 boot-completed.sh(非阻塞)

Late-load 专用脚本 ​

模块可以提供 late-load.sh 脚本,该脚本仅在 late-load 模式下运行,作为 post-fs-data.sh 的替代。该脚本在模块挂载之前运行,与标准流程中的 post-fs-data.sh 时机类似。

此外,通用脚本可以放置在 /data/adb/late-load.d/ 目录下,在该阶段执行。

在脚本中检测 late-load 模式 ​

模块可以通过 KSU_LATE_LOAD 环境变量检测当前是否处于 late-load 模式:

sh
if [ "$KSU_LATE_LOAD" = "1" ]; then
    # 当前处于 late-load 模式
    echo "Late-load mode detected"
fi

这使得模块可以据此调整自身行为,例如跳过仅在早期启动时才需要的操作。

模块管理便利 ​

ShizuSU 提供了一系列模块管理上的便利功能:

  • 备份与恢复:一键备份/恢复已安装模块与 root 白名单。
  • 批量安装:一次安装多个模块 zip;单个安装失败不会中断整个流程,失败项会被收集并在结束时汇报。
  • 批量管理:支持一键启用、禁用、全部禁用与全部卸载,方便批量维护模块环境。

在 GPL3 许可证下发布。