在Android 9.0及以上版本中,由于系统对安全要求越来越严格,不允许app往/sys/class这样的系统节点写入数据,即使是系统应用也没有这个权限了。这给一些需要控制硬件设备的app带来了困难。例如,如果app需要控制LED灯的亮度,但是没有节点权限,怎么办呢?本文将介绍一种解决方案,利用init.rc文件中的write命令和on property触发器,来实现在app中正确往系统节点写数据的功能。
系列文章
Android系统 init.rc 第一次开机创建文件节点实现和原理分析
为什么没有权限写入?
在Android 9.0及以上版本中,Google对系统进行了一些修改,增加了对文件系统的加密和保护。这意味着一些敏感的目录,如/data, /mnt等,不能被任意访问和修改。只有在特定的阶段,才能对这些目录进行操作。
而/sys/class目录是一个特殊的目录,它是一个虚拟文件系统,用于提供内核和用户空间之间的通信接口。它包含了一些硬件设备的状态和控制信息,如LED灯、电池、温度等。如果任何app都可以随意往这些节点写入数据,可能会造成系统的不稳定或安全风险。因此,Google限制了对/sys/class目录的访问权限,只有root用户或者拥有特定SELinux策略的进程才能操作这些节点。
从app的角度来看,app运行在一个沙盒环境中,它只能访问自己的数据目录和一些公共目录。它不能直接访问/sys/class目录或其他系统目录。即使是系统应用,也需要申请一些特殊的权限或者签名才能访问这些目录。因此,在Android 9.0及以上版本中,app往/sys/class目录写入数据是不可能的。
从adb的角度来看,adb是一个调试工具,它可以通过USB或网络连接到Android设备上,并执行一些命令或操作。adb可以以root用户或者shell用户身份运行。如果以root用户身份运行,adb可以访问/sys/class目录并往其中写入数据。但是,在Android 9.0及以上版本中,默认情况下adb是以shell用户身份运行的,并且不能切换到root用户身份。除非你对设备进行了root操作或者解锁了bootloader,并且安装了一个支持root的内核或者ROM。因此,在Android 9.0及以上版本中,默认情况下adb也不能往/sys/class目录写入数据。
解决方案
解决方案1: 把设备root
如果你要在adb shell去写入 请看方案2
如果你要在app 去写入/sys/class 节点 , 你需要设备root权限 , 方案非常多
可以看我另外一篇: 链接: Android13 Root实现和原理分析。
然后在网上随便抄个shell utils工具类 , 通过root执行 非常简单 不过多赘述 , 如果实在不清楚 可以留言私信。
ShellUtils.execCmd("echo '2 0 1' > /sys/devices/platform/leds/leds/led ", true);
解决方案2: 把节点权限改成
1|rk3568_r:/sys # ls -ll devices/platform/leds/leds/led -rw-r--r-- 1 root root 4096 2023-07-27 17:38:16.813334118 +0800 devices/platform/leds/leds/led
在Android系统中,节点的权限是由设备驱动在创建节点时设置的,通常情况下,这些权限是固定的,不能被修改。在我的例子中,节点的权限是rw-r--r--
,这意味着只有root用户可以写入这个节点,其他用户只能读取这个节点。
如果你想让普通app和adb shell能写入这个节点,你需要修改设备驱动的代码,让它在创建节点时设置更宽松的权限。但是,这可能会带来安全风险,因为任何app都可以修改这个节点,可能会导致系统不稳定或者硬件设备损坏。
如果你的设备已经root,你可以使用chmod
命令来修改节点的权限。例如,你可以使用以下命令来修改节点的权限:
chmod 777 /sys/devices/platform/leds/leds/led
这个命令会把节点的权限修改为rwxrwxrwx
,这意味着任何用户都可以读取和写入这个节点。
但是,需要注意的是,这个修改只是临时的,当设备重启后,节点的权限会恢复到原来的状态。如果你想让这个修改永久生效,你需要修改设备驱动的代码,或者在init.rc文件中添加一个命令,让系统在启动时自动修改节点的权限。
# 这里证明, 已经修改了权限 也验证了 之前默认adb shell是无法写入的 , 我们尝试在init.rc 修改了权限777 所有用户均可读写。 rk3568_r:/ # ls -ll /sys/devices/platform/leds/leds/led -rwxrwxrwx 1 root root 4096 2023-07-28 10:51:24.256688797 +0800 /sys/devices/platform/leds/leds/led rk3568_r:/ # exit rk3568_r:/ $ echo "2 0 1" > /sys/devices/platform/leds/leds/led
解决方案3: 通过on property触发器
既然app和adb都不能直接往/sys/class目录写入数据,那么我们是否有其他方法可以实现这个功能呢?答案是有的。我们可以利用init.rc文件中的write命令和on property触发器来实现这个功能。
init.rc文件是Android系统启动时由init进程读取的初始化配置文件,用于启动Android中的各种服务,以及配置系统参数。init.rc文件使用一种类似于shell脚本的语法,
在init.rc文件中,我们可以使用以下两种命令来实现我们的目的:
- write [ ]*:向指定的文件写入一个或多个字符串。例如,write /sys/class/leds/brightness 0,就是向/sys/class/leds/brightness文件写入0,表示关闭LED灯。
- on property:=:当指定的系统属性的值变化时,执行相应的动作。例如,on property:persist.sys.enable=1,就是当persist.sys.enable属性的值变为1时,执行后面的动作。
结合这两种命令,我们可以在init.rc文件中添加一些代码,来监听app设置的系统属性的变化,并根据不同的值来往/sys/class目录写入数据。例如,如果我们想控制LED灯的亮度,我们可以在app中使用SystemProperties.set(“persist.sys.led”, “0”)或SystemProperties.set(“persist.sys.led”, “1”)来设置persist.sys.led属性的值。然后,在init.rc文件中添加以下代码:
# 监听persist.sys.led属性的变化 on property:persist.sys.led=0 # 当persist.sys.led属性的值为0时,关闭LED灯 write /sys/class/leds/leds/led "2 0 1" on property:persist.sys.led=1 # 当persist.sys.led属性的值为1时,打开LED灯 write /sys/class/leds/leds/led "2 0 0"
这样,当app设置了persist.sys.led属性的值后,init进程就会根据这个值来往/sys/class/leds/leds/led文件写入数据,从而控制LED灯的亮度。
为什么app和adb无法访问,而init.rc可以写入?
这是因为init进程在系统启动时以root用户身份运行,它有权限访问/sys/class目录并往其中写入数据。而app和adb默认是以非root用户身份运行的,它们没有权限访问/sys/class目录。这是Android系统为了保护系统安全和稳定性,防止非授权的访问和修改系统节点而设置的限制。
怎么解决写入节点值是动态的如何传参?
如果需要传递参数,可以在write命令后面添加参数。例如,如果需要控制LED灯的亮度和颜色,可以在app中使用SystemProperties.set(“persist.sys.led”, “1 0 1”)或SystemProperties.set(“persist.sys.led”, “1 0 0”)来设置persist.sys.led属性的值。然后,在init.rc文件中添加
以下代码:
# 监听persist.sys.led属性的变化 on property:persist.sys.led=* # 当persist.sys.led属性的值变化时,根据其值来控制LED灯 write /sys/class/leds/leds/led ${persist.sys.led}
这样,当app设置了persist.sys.led属性的值后,init进程就会根据这个值来往/sys/class/leds/leds/led文件写入数据,从而控制LED灯的亮度和颜色。
测试验证
130|rk3568_r:/ # setprop persist.sys.led "2 0 0" //我们尝试写入把第3组的第1个LED打开 rk3568_r:/ # getprop persist.sys.led //查看一下写入的值 2 0 0 rk3568_r:/ # logcat | grep persist.sys.led //可以看到action已经被触发 01-01 08:00:06.017 0 0 E init : Could not set 'persist.sys.led' to '2 >> out/target/product/rk3568_r/vendor/build.prop; echo 0 >> out/target/product/rk3568_r/vendor/build.prop; echo 0' while loading .prop filesProperty value too long 07-28 10:48:43.868 0 0 I init : processing action (persist.sys.led=*) from (/system/etc/init/hw/init.rc:535) 07-28 10:49:56.382 0 0 I init : processing action (persist.sys.led=*) from (/system/etc/init/hw/init.rc:535) 07-28 10:50:37.042 0 0 I init : processing action (persist.sys.led=*) from (/system/etc/init/hw/init.rc:535) 07-28 10:51:19.331 0 0 I init : processing action (persist.sys.led=*) from (/system/etc/init/hw/init.rc:535) 07-28 10:51:24.381 0 0 I init : processing action (persist.sys.led=*) from (/system/etc/init/hw/init.rc:535) ^C 130|rk3568_r:/ # dmesg | grep led_ //这是我加的driver打印 证明已经写入了 [ 4.399010] 11 led_probe 55 [ 4.399179] led_init_state to request GPIO pin 14 for LED 0 color 0 [ 4.399197] led_init_state to request GPIO pin 13 for LED 0 color 1 [ 4.399213] led_init_state to request GPIO pin 19 for LED 1 color 0 [ 4.399229] led_init_state to request GPIO pin 18 for LED 1 color 1 [ 4.399245] led_init_state to request GPIO pin 91 for LED 2 color 0 [ 4.399260] led_init_state to request GPIO pin 97 for LED 2 color 1 [ 4.399274] led_init_state to request GPIO pin 59 for LED 3 color 0 [ 4.399288] led_init_state to request GPIO pin 58 for LED 3 color 1 [ 4.399303] led_init_state to request GPIO pin 57 for LED 4 color 0 [ 4.399317] led_init_state to request GPIO pin 56 for LED 4 color 1 [ 4.399331] led_init_state to request GPIO pin 99 for LED 5 color 0 [ 4.399345] led_init_state to request GPIO pin 98 for LED 5 color 1 [ 4.399361] led_init_state to request GPIO pin 94 for LED 6 color 0 [ 4.399376] led_init_state to request GPIO pin 90 for LED 6 color 1 [ 4.399390] led_init_state to request GPIO pin 101 for LED 7 color 0 [ 4.399404] led_init_state to request GPIO pin 100 for LED 7 color 1 [ 4.399418] led_init_state to request GPIO pin 5 for LED 8 color 0 [ 4.399432] led_init_state to request GPIO pin 0 for LED 8 color 1 [ 5.907318] selinux: SELinux: Loaded policy from /odm/etc/selinux/precompiled_sepolicy rk3568_r:/ # 此时我看设备上的灯已经亮了
注意
虽然这个解决方案可以有效地实现我们的目的,但是也有一些注意事项:
- 不要在init.rc文件中添加过多的on property触发器,可能会影响init进程的性能和内存占用。最好只添加你需要的触发器,并且尽量简化触发器中的动作。
- 正常情况下推荐是方案3 , 一般设备是不root的 , 同时也能避免有些sb驱动 不改driver权限的问题。
- 验证发现如果打开SELINUX的话 普通app和adb shell 是压根没有权限写prop的。
- 后面我跟了下代码流程发现这里是检测SELINUX权限的 会检测UID等信息, 所以我们屏蔽掉 这样普通app和adb shell都能执行写入prop。这种不是正规的搞法 适合不过GMS的源码 正常情况还是要去写上下文的。
总结
本文介绍了一种在Android 9.0及以上版本中解决app往/sys/class目录写不进数据的问题的方案,即利用init.rc文件中的write命令和on property触发器,来实现在app中正确往系统节点写数据的功能。本文也分析了这个方案的原理和注意事项,
希望对你有所帮助。如果你有任何问题或建议,欢迎留言讨论。谢谢