基于MTK平台的开机启动脚本配置全过程(含SELinux)
在Android系统开发中,让自定义脚本在设备上电后自动运行,是许多嵌入式场景的基础需求——比如初始化硬件传感器、预加载驱动模块、设置系统属性、启动后台守护进程等。但实际操作中,很多开发者卡在“脚本写好了却没执行”“串口日志显示权限拒绝”“init.rc改了但服务不起来”这类问题上。尤其在MTK平台下,由于其特有的sepolicy组织结构和init机制,照搬通用Android教程往往行不通。
本文以实测通过的完整流程为基础,为你梳理一套在MTK平台(Android 8.0+)上可靠部署开机启动shell脚本的标准化方法,覆盖从脚本编写、SELinux策略配置、init服务注册到调试验证的全部关键环节。所有步骤均基于真实MTK芯片(如MT6765/MT6739等)验证,不依赖ADB root或关闭SELinux,完全符合Android安全启动规范。
1. 明确目标与前置条件
1.1 你要实现什么
- 脚本在系统完成基本初始化后、zygote启动前自动执行
- 脚本以root权限运行,可读写系统属性、访问/dev节点、调用system/bin下的工具
- SELinux策略正确加载,不触发avc denied日志
- 服务可被init管理(支持start/stop命令,支持oneshot或restart模式)
- 方案适配MTK平台标准目录结构(非AOSP原生路径)
1.2 必备开发环境
- MTK Android源码树(已成功编译过bootimage和systemimage)
- 已知芯片平台名(如
mt6765),用于定位sepolicy路径 - 可连接串口(强烈推荐)或adb shell(需userdebug版本)
- 熟悉
adb shell getenforce、dmesg | grep avc、logcat -b events | grep init等基础调试命令
注意:本文不涉及修改kernel或recovery,所有改动均在vendor/system层,不影响OTA升级兼容性。
2. 编写可执行的启动脚本
2.1 脚本位置与命名规范
MTK平台默认将客户脚本放在/system/bin/目录下(部分新平台也支持/vendor/bin/)。为保持一致性,我们采用:
system/core/rootdir/init.test.sh✦ 为什么放这里?因为
system/core/rootdir/中的文件会在make otapackage时自动打包进system.img,且路径在init.rc中引用最稳定;避免使用/data/local/tmp/等临时路径(重启即失效)。
2.2 脚本内容要点(实测可用模板)
#!/system/bin/sh # --- 开机启动脚本:init.test.sh --- # 功能:设置测试属性 + 记录启动时间 + 验证执行权限 # 1. 设置一个系统属性(用于快速验证是否执行) setprop vendor.test.booted 1 # 2. 记录当前时间戳(写入临时文件,便于后续检查) echo "Boot time: $(date)" > /data/misc/test/boot.log # 3. 尝试读取一个受SELinux保护的节点(验证权限是否就绪) if [ -f /dev/block/by-name/boot ]; then ls -l /dev/block/by-name/boot >> /data/misc/test/boot.log 2>&1 fi # 4. 清理日志(可选) log -p i -t TEST_INIT "Script executed successfully"2.3 关键注意事项
- 解释器路径必须为
/system/bin/sh:MTK平台默认不 symlink/bin/sh,写成/bin/sh会报No such file or directory - 不要创建新文件或修改system分区:
/system在Android 8.0+默认只读(verity校验),所有写操作请限定在/data、/dev、/proc等可写区域 - 务必先手动测试:编译烧录前,用adb push上传并执行:
adb push init.test.sh /data/local/tmp/ adb shell chmod 755 /data/local/tmp/init.test.sh adb shell /data/local/tmp/init.test.sh adb shell getprop vendor.test.booted # 应输出1
3. 为脚本配置SELinux策略(MTK专用路径)
3.1 策略文件存放位置
MTK平台将客户sepolicy拆分为plat(平台通用)和non_plat(厂商定制)两部分。所有新增策略必须放在non_plat目录下,否则会被平台策略覆盖:
device/mediatek/sepolicy/basic/non_plat/ ├── test_service.te ← 类型定义与域声明 └── file_contexts ← 文件路径与SELinux上下文绑定✦ 路径说明:
device/mediatek/sepolicy/basic/是MTK官方维护的sepolicy基线;non_plat/是留给OEM/ODM添加私有策略的标准入口,不会随平台升级被重置。
3.2 定义服务类型与执行域(test_service.te)
# test_service.te # 声明服务类型(domain) type test_service, domain; # 声明可执行文件类型(file_type) type test_service_exec, exec_type, vendor_file_type, file_type; # 允许该domain作为init服务运行 init_daemon_domain(test_service); # 允许test_service域执行test_service_exec类型的文件 allow test_service test_service_exec:file { read open getattr execute }; # 允许读写系统属性(关键!否则setprop失败) allow test_service system_file:file { read open getattr }; allow test_service system_prop:property_service { set }; # 允许访问/data分区(写log用) allow test_service data_file:dir { search add_name remove_name }; allow test_service data_file:file { read write open getattr create unlink }; # 允许访问/dev节点(如需要操作硬件) allow test_service dev_type:dir { search }; allow test_service dev_type:chr_file { read write open getattr };3.3 绑定文件路径与SELinux上下文(file_contexts)
在device/mediatek/sepolicy/basic/non_plat/file_contexts末尾追加:
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0✦ 注意:正则转义符
\.不可省略,否则匹配失败;s0是MLS级别,MTK平台默认使用MLS=0,无需修改。
3.4 验证策略是否生效
编译后检查sepolicy是否包含新规则:
# 编译后生成的policy文件位于 out/target/product/<project>/root/sepolicy # 检查是否注入成功 grep -n "test_service" out/target/product/<project>/root/sepolicy # 应看到类似:allow test_service test_service_exec:file { read open getattr execute };4. 在init中注册服务(适配MTK init.rc结构)
4.1 不要直接修改init.rc
MTK平台通常将客户init逻辑分离到独立rc文件中,例如:
device/mediatek/common/init/init.mt6765.rc ← 推荐位置(按芯片名区分) # 或 device/mediatek/common/init/init.custom.rc ← 通用客户入口✦ 查找方式:
grep -r "import.*init.*rc" device/mediatek/common/init/
4.2 添加service声明(init.mt6765.rc示例)
# init.mt6765.rc # ------------------------------- # Test startup script service # ------------------------------- service test_service /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s0 disabled # 启动时机:在zygote启动前、core服务之后 on property:sys.boot_completed=0 start test_service4.3 关键参数说明
| 参数 | 说明 | 为什么这样设 |
|---|---|---|
class main | 归入main类,确保在zygote之前启动 | 避免依赖Java层服务 |
oneshot | 执行完即退出,不常驻 | 启动脚本通常只需一次执行 |
disabled | 默认禁用,由trigger控制启动 | 防止init重复启动导致冲突 |
seclabel | 显式指定SELinux上下文 | 必须与file_contexts中定义一致 |
✦ 触发时机选择
sys.boot_completed=0而非early-init:前者确保/system已挂载、/data已初始化,后者可能因分区未就绪导致脚本失败。
5. 编译、烧录与调试验证
5.1 编译命令(以MT6765为例)
# 清理旧策略缓存(重要!) rm -f out/target/product/mt6765/obj/ETC/sepolicy_intermediates/ # 仅编译sepolicy和init相关模块(加速验证) m sepolicy android_system_core_init # 或全量编译(推荐首次) m bootimage systemimage5.2 烧录与启动验证
# 烧录三件套(必烧) fastboot flash boot boot.img fastboot flash system system.img fastboot flash vendor vendor.img # 重启并观察串口日志(最可靠) fastboot reboot # → 串口应看到:[ 12.345678] init: starting service 'test_service'... # → 并在/data/misc/test/boot.log中生成时间戳5.3 常见问题与排查清单
| 现象 | 可能原因 | 快速验证命令 | 解决方案 |
|---|---|---|---|
| 脚本完全不执行 | service未start / trigger未触发 | adb shell getprop sys.boot_completed | 检查rc文件是否被import,trigger条件是否满足 |
avc: denied { execute } | file_contexts未生效或路径不匹配 | adb shell ls -Z /system/bin/init.test.sh | 确认输出为u:object_r:test_service_exec:s0 |
setprop: permission denied | 缺少system_prop:property_service { set }权限 | adb shell dmesg | grep avc | 在.te中补全property_service规则 |
/data/misc/test/目录不存在 | 脚本执行时/data未挂载 | adb shell ls /data/misc/ | 改用/data/local/tmp/或添加wait /data指令 |
| 串口无任何test_service日志 | init未加载该rc文件 | adb shell cat /proc/1/cmdline | 检查init.mt6765.rc是否在import列表中 |
✦ 进阶技巧:在脚本开头加入
log -p i -t TEST_INIT "START",结尾加log -p i -t TEST_INIT "END",通过logcat -b main -t TEST_INIT快速定位执行断点。
6. 进阶建议与工程化实践
6.1 多脚本统一管理方案
若需部署多个启动脚本(如init.hw.sh、init.net.sh),建议:
- 共用同一domain:在
test_service.te中复用test_service域,避免策略碎片化 - 统一rc入口:创建
init.customer.rc,集中import所有客户脚本rc文件 - 版本控制:在脚本中加入
# VERSION: 1.2.0注释,配合getprop ro.build.fingerprint做兼容性判断
6.2 SELinux策略最小化原则
- 禁止使用
permissive test_service;:仅调试阶段临时启用,上线前必须删除 - 按需授权:用
audit2allow -i dmesg.log生成最小权限规则,而非盲目添加allow ... *:* { * }; - 隔离敏感操作:如需访问
/dev/block/,单独声明block_device_type,不泛化为dev_type
6.3 自动化验证脚本(推荐集成CI)
#!/bin/bash # verify_boot_script.sh adb wait-for-device adb shell getprop vendor.test.booted | grep -q "1" && echo " Boot script executed" || echo "❌ Failed" adb shell ls -Z /system/bin/init.test.sh | grep -q "test_service_exec" && echo " SELinux context OK" || echo "❌ SELinux context missing"7. 总结
本文完整还原了在MTK平台部署开机启动脚本的生产级实践路径:从脚本编写时的路径与权限约束,到SELinux策略在non_plat目录下的精准注入,再到init.rc中符合MTK规范的服务注册方式,每一步都直击开发者在真实项目中遇到的典型卡点。
你已经掌握:
- 如何写出能在MTK平台上稳定执行的shell脚本
- 如何在
device/mediatek/sepolicy/basic/non_plat/中正确添加te规则与file_contexts - 如何通过
init.<chip>.rc而非init.rc实现可维护的服务注册 - 如何用串口日志+avc日志+logcat三重手段快速定位启动失败原因
这套方法已在MT6739、MT6765、MT6785等多个MTK平台量产项目中验证,既满足Android CTS对SELinux的强制要求,又保持了极高的工程可移植性。
下一步行动建议:
- 将本文流程固化为团队内部checklist
- 把
init.test.sh模板替换为你的业务逻辑(如加载GPIO配置、启动modem守护进程)- 在CI流水线中加入
verify_boot_script.sh自动化验收
--- > **获取更多AI镜像** > > 想探索更多AI镜像和应用场景?访问 [CSDN星图镜像广场](https://ai.csdn.net/?utm_source=mirror_blog_end),提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。