Android开机启动脚本命名规范与路径说明
在Android系统定制开发中,实现自定义服务或工具的开机自动运行是常见需求。但很多开发者在首次尝试时会遇到脚本不执行、权限拒绝、SELinux拦截等问题,其中很大一部分原因源于对脚本命名规则和存放路径缺乏系统理解。本文不讲抽象原理,只聚焦一个核心问题:你的开机启动脚本该叫什么名字?该放在哪里?为什么必须这样?所有内容均基于Android 8.0及以上版本实测验证,适用于高通、MTK等主流平台,已在“测试开机启动脚本”镜像中完成全流程验证。
1. 脚本命名不是随便起的:4条硬性规范
很多人以为init.xxx.sh、xxx.sh、start.sh都可以,其实Android init机制对脚本名称有明确约束。命名错误会导致init进程根本不会识别该文件,更谈不上执行。
1.1 必须以init.开头,且后缀为.sh
这是Android init解析器的硬编码规则。init进程在扫描/system/bin/等目录时,仅识别符合init.*.sh模式的可执行文件(注意:不是所有.sh文件,而是必须带init.前缀)。
- 正确示例:
init.test.sh、init.network.sh、init.logrotate.sh - ❌ 错误示例:
test.sh(无init.前缀)、init_test.sh(下划线非点号)、init.test(无.sh后缀)
关键提示:这个规则写死在
system/core/init/builtins.cpp的do_chdir和import_parser相关逻辑中,并非约定俗成。即使你用chmod +x赋予执行权限,test.sh也永远不会被init加载。
1.2 名称中禁止使用空格、中文、特殊符号(除点号和字母数字外)
init解析器使用空格作为参数分隔符,若脚本名含空格(如init my service.sh),会被截断为init、my、service.sh三段,导致路径错误。同理,中文字符在UTF-8与ASCII混合环境中极易引发编码解析失败。
- 安全组合:小写字母、数字、点号(
.) - ❌ 高危字符:空格、
@、#、$、%、&、*、(、)、+、=、{、}、[、]、|、\、;、:、"、'、<、>、,、?、/、~、^、-(连字符在部分旧版本中也有兼容问题,建议避免)
1.3 避免与系统已有init脚本重名
Android系统自带大量init.*.sh脚本,如init.qcom.rc、init.mdm.sh、init.recovery.sh等。若你的脚本命名为init.qcom.sh,可能被系统覆盖或引发冲突。
- 推荐做法:在
init.后加入项目缩写或功能标识,如init.csdn_test.sh、init.myservice.sh - ❌ 风险操作:直接复用芯片厂商命名空间(如
init.mtk.sh、init.qualcomm.sh)
1.4 名称长度建议控制在24字符以内
虽然Linux文件系统支持长文件名,但init进程在早期解析阶段使用固定长度缓冲区(通常为32字节)。过长名称可能导致截断,尤其在带路径拼接时(如/system/bin/init.very_long_custom_script_name.sh)易触发边界错误。
- 实践建议:
init.test.sh(12字符)、init.netmon.sh(13字符)足够清晰且安全 - 警惕:
init.custom_network_monitor_service_v2.sh(37字符)存在潜在风险
2. 脚本存放路径:3个合法位置与选择逻辑
脚本命名正确只是第一步,放错位置同样无法启动。Android init仅从特定目录加载脚本,且不同目录对应不同执行时机与权限上下文。
2.1 首选路径:/system/bin/—— 系统级服务,root权限,最稳定
这是官方推荐且兼容性最好的路径。/system/bin/下的脚本由init进程以root用户身份直接执行,无需额外配置用户组,SELinux上下文也最易适配。
- 适用场景:需要访问硬件设备节点(如
/dev/ttyS0)、修改系统属性(setprop)、启动守护进程(daemon) - 实操步骤:
# 在PC端编译好脚本后push adb root adb remount adb push init.test.sh /system/bin/ adb shell chmod 755 /system/bin/init.test.sh- 注意:需确保
/system分区已remount为可写,且脚本必须有+x权限
2.2 次选路径:/vendor/bin/—— 厂商扩展区,适合SoC定制化功能
在Android Treble架构下,/vendor分区独立于/system,专供芯片厂商放置驱动、HAL及配套脚本。将脚本放在此处可避免系统升级时被覆盖。
- 适用场景:MTK/高通平台特有的初始化逻辑(如基带校准、射频参数加载)
- SELinux适配关键:必须在
file_contexts中声明类型,例如:
/vendor/bin/init.test.sh u:object_r:test_service_exec:s0- ❌ 常见误区:直接放入
/vendor/bin/却不更新SELinux策略,会导致avc: denied { execute }错误,即使关闭SELinux也会因file_contexts缺失而失败。
2.3 特殊路径:/data/local/tmp/—— 仅限调试,不可用于量产
此路径无需recovery刷机或adb remount,适合快速验证脚本逻辑是否正确。但因其属于用户数据分区,重启后内容可能被清除,且init默认不从此处加载脚本。
- 调试技巧:在
init.rc中临时添加一行指向该路径(仅测试用):
service test_debug /data/local/tmp/init.test.sh class main user root group root oneshot seclabel u:object_r:shell_exec:s0- ❌ 严禁行为:将此路径用于正式开机启动,因
/data可能延迟挂载,导致脚本执行失败。
3. init.rc服务声明:路径、权限与执行时机三要素
脚本文件就位后,必须通过init.rc或其导入文件声明服务。声明错误是开机不执行的第二大原因。
3.1 路径必须与实际存放位置严格一致
service指令后的路径是绝对路径,不能省略/system/bin/等前缀。常见错误是写成service test /init.test.sh(缺少/system/bin/)或service test init.test.sh(相对路径)。
- 正确写法(脚本在
/system/bin/):
service test_service /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s0- ❌ 错误写法:
service test_service init.test.sh(init会尝试在当前工作目录找,通常是/,找不到)service test_service ./init.test.sh(不支持.语法)
3.2 用户与组设置决定脚本能访问的资源范围
user root:可访问所有设备节点、修改系统属性、绑定特权端口(<1024)user system:权限受限,无法操作/dev/下多数节点group root:必须显式声明,否则即使user root也无法获得root组权限(影响/dev/节点访问)
实测案例:某脚本需向
/dev/block/mmcblk0p1写入数据,仅设user root但未设group root,报错Permission denied;添加group root后正常。
3.3oneshot与disabled标志决定启动时机与重试逻辑
oneshot:执行一次即退出,适合初始化类脚本(如setprop、创建临时文件)。这是绝大多数开机脚本的正确选择。disabled:默认不启动,需通过start <service_name>命令手动触发。可用于按需启动的调试服务。- ❌ 禁止对shell脚本使用
restart或class main以外的class:class late_start等在脚本执行阶段尚未就绪,会导致超时失败。
4. SELinux策略:3个必须配置的文件与1个调试技巧
在Android 8.0+强制启用SELinux的环境下,缺少策略配置是脚本静默失败的最常见原因。策略涉及三个文件,缺一不可。
4.1test_service.te:定义域类型与基础权限
此文件声明脚本运行的SELinux域(domain)及其可执行类型。内容必须包含三要素:
# 定义服务域(必须与init.rc中seclabel一致) type test_service, coredomain; # 定义可执行文件类型(必须与file_contexts中一致) type test_service_exec, exec_type, vendor_file_type, file_type; # 允许init以该域启动服务 init_daemon_domain(test_service); # 关键:允许该域执行自身文件 allow test_service test_service_exec:file { read open getattr execute };注意:
init_daemon_domain宏已隐含allow init test_service:process transition;,无需重复声明。
4.2file_contexts:绑定文件路径与SELinux类型
此文件将物理路径映射到SELinux类型,是init加载脚本前的必经校验。路径必须精确匹配,包括末尾斜杠(如有)。
- 正确(脚本在
/system/bin/):
/system/bin/init.test.sh u:object_r:test_service_exec:s0- 正确(脚本在
/vendor/bin/):
/vendor/bin/init.test.sh u:object_r:test_service_exec:s0- ❌ 错误:
/system/bin/init.*.sh(通配符不被file_contexts支持)、/system/bin/(目录级匹配无效)
4.3sepolicy加载位置:non_plat vs plat 的选择逻辑
non_plat目录(如device/mediatek/sepolicy/basic/non_plat/):存放芯片厂商或OEM定制策略,优先级高于plat,且不会被AOSP更新覆盖。强烈推荐将自定义策略放在此处。plat目录:AOSP标准策略,修改后可能被上游更新冲掉。
4.4 调试技巧:串口日志比logcat更直接
当脚本无输出时,adb logcat往往捕获不到init早期错误。连接串口(UART)并执行:
# 在串口终端中查看实时avc拒绝日志 dmesg | grep avc每条avc: denied日志都明确指出缺失的权限,可直接转化为allow规则。例如:
avc: denied { write } for name="test.prop" dev="tmpfs" ino=12345 scontext=u:r:test_service:s0 tcontext=u:object_r:shell_prop:s0 tclass=file→ 补充规则:allow test_service shell_prop:file write;
5. 完整验证流程:5步确认脚本真正生效
命名、路径、init.rc、SELinux全部配置完成后,必须按顺序验证,避免遗漏环节。
5.1 第一步:手动执行验证脚本逻辑
adb shell su /system/bin/init.test.sh echo $? # 检查返回值,0表示成功 getprop test.prop # 检查脚本内setprop是否生效通过:说明脚本语法、路径、权限均正确
❌ 失败:先解决此步,再进行后续
5.2 第二步:检查init.rc是否被正确加载
adb shell cat /proc/1/cmdline # 确认init进程启动参数 ls -l /system/etc/init/ # 查看是否有对应.rc文件 getenforce # 确认SELinux状态(Enforcing/Permissive)5.3 第三步:确认服务已注册但未启动
adb shell getprop | grep init.svc.test_service # 应返回空(未启动) dumpsys activity services | grep test_service # 应无输出5.4 第四步:手动触发服务并观察状态
adb shell start test_service sleep 2 getprop init.svc.test_service # 应返回"stopped"(oneshot特性) getprop test.prop # 应返回脚本中设置的值5.5 第五步:重启验证全自动执行
adb reboot # 等待开机完成 adb shell getprop test.prop # 重启后仍应有值,证明开机自动执行成功6. 总结:命名与路径的黄金法则
回顾全文,Android开机启动脚本的命名与路径并非随意选择,而是由init进程设计逻辑、SELinux强制策略、Treble分区架构共同决定的工程约束。记住这三条黄金法则,可避开90%的坑:
- 命名法则:
init.开头 + 小写字母数字点号 +.sh结尾 + 长度≤24字符,杜绝一切非常规字符; - 路径法则:
/system/bin/为首选,/vendor/bin/为次选,/data/local/tmp/仅限调试,路径必须与init.rc中声明的绝对路径完全一致; - 验证法则:不跳过任何一步验证,从手动执行→服务触发→重启测试,层层递进,用
getprop和dmesg代替猜测。
遵循这些规范,你的“测试开机启动脚本”镜像就能稳定可靠地完成每一次开机初始化任务。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。