第二章:Android串口权限配置
好,咱们接着聊。上一章我们把串口硬件和驱动层面的事情理清了,这一章要解决一个很现实的问题——你的App凭什么能访问串口?
说白了,Android系统对硬件访问管得很严。你写个App想直接打开/dev/ttyS0?没门。系统会告诉你“Permission denied”。我刚开始做串口开发时,就被这个权限问题卡了整整两天。
2.1 root权限获取
先说说最直接的办法——root。拿到root权限,就等于拿到了系统的最高控制权。你的App可以想干嘛就干嘛,串口随便开。
2.1.1 判断是否已root
怎么知道设备有没有root?写个简单的方法试试:
public static boolean isDeviceRooted() {
String[] paths = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/system/sd/xbin/su"
};
for (String path : paths) {
if (new File(path).exists()) {
return true;
}
}
return false;
}
嗯,这个方法不完美,但够用。有些设备会伪装su文件,不过咱们做嵌入式开发,一般不会遇到那种情况。
2.1.2 动态申请root权限
如果你的App需要临时提权,可以用Runtime执行su命令:
public static boolean grantRootPermission() {
try {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
os.writeBytes("chmod 666 /dev/ttyS0\n");
os.writeBytes("exit\n");
os.flush();
process.waitFor();
return process.exitValue() == 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
这里有个坑——用户设备上会弹出root授权弹窗。如果用户点了“拒绝”,那后面所有操作都白搭。我曾经在一个项目里没处理这个情况,结果现场演示时翻车了……
2.2 SELinux策略修改
root搞定了,但有时候还是打不开串口。为什么?因为SELinux在背后拦着。
SELinux是Linux内核的安全模块,Android从4.3开始强制启用。它比传统的Linux权限模型更严格——就算你是root,SELinux说不让你访问,你就访问不了。
2.2.1 临时关闭SELinux
调试时最省事的办法:
adb shell setenforce 0
这条命令把SELinux设为Permissive模式。它只记录违规行为,不阻止操作。我习惯在开发板上先跑这条命令,确认串口能正常工作,再去细化策略。
2.2.2 永久修改SELinux策略
要永久生效,得改SELinux策略文件。以串口设备/dev/ttyS0为例:
# 创建策略文件 uart.te
type uart_device_t;
dev_type(uart_device_t)
# 允许init进程访问
allow init uart_device_t:chr_file rw_file_perms;
# 允许system_app访问
allow system_app uart_device_t:chr_file rw_file_perms;
然后编译并加载:
make -j8
adb push out/target/product/xxx/ramdisk.img /tmp/
adb shell reboot
嗯,编译SELinux策略是个技术活。我建议你第一次做的时候,先找个现成的平台参考一下。我自己就吃过亏——写错一个allow规则,系统直接起不来。
2.2.3 查看SELinux日志
如果串口还是打不开,先查查SELinux有没有拦截:
adb shell dmesg | grep avc
adb logcat -b events | grep avc
看到类似这样的日志,就是被拦了:
avc: denied { read write } for pid=1234 comm="app_process"
name="ttyS0" dev="tmpfs" ino=5678
scontext=u:r:untrusted_app:s0
tcontext=u:object_r:device:s0 tclass=chr_file
这条日志告诉你:哪个进程(pid=1234)、想干什么(read write)、被谁拦了(untrusted_app)。拿着这些信息去写allow规则就行。
2.3 串口节点读写权限设置
root和SELinux都搞定了,最后一步——确保串口节点本身有正确的读写权限。
2.3.1 查看当前权限
adb shell ls -l /dev/ttyS0
正常输出应该是:
crw-rw---- 1 root dialout 4, 64 2024-01-01 10:00 /dev/ttyS0
看到没?权限是660,属主是root,属组是dialout。如果你的App不在dialout组里,就打不开。
2.3.2 修改权限的几种方式
方式一:chmod(临时)
adb shell chmod 666 /dev/ttyS0
简单粗暴,但重启后失效。我一般只在调试时用。
方式二:修改udev规则(推荐)
在/etc/udev/rules.d/下创建规则文件:
# /etc/udev/rules.d/99-serial.rules
KERNEL=="ttyS0", MODE="0666", GROUP="dialout"
这样每次设备插入或系统启动时,udev会自动设置权限。我在好几个项目里都用这个方案,稳定得很。
方式三:init.rc中设置
如果你的设备用init.rc管理服务,可以加一条:
on boot
chmod 0666 /dev/ttyS0
chown system system /dev/ttyS0
这种方式适合定制ROM的场景。我帮客户做平板项目时,就是这么干的。
2.3.3 权限检查清单
| 检查项 | 命令 | 期望结果 |
|---|---|---|
| 设备节点存在 | ls -l /dev/ttyS0 | crw-rw---- 或 crw-rw-rw- |
| SELinux状态 | getenforce | Permissive 或 Enforcing(有策略) |
| App所属组 | id | 包含dialout或root |
| root权限 | su -c "id" | uid=0(root) |
2.4 避坑指南
最后分享几个我踩过的坑:
- 别用777权限——虽然能解决问题,但安全风险太大。我见过有人把整个/dev目录都设成777,结果系统被入侵了。
- 注意SELinux策略的优先级——就算你chmod 666了,SELinux照样能拦你。这两个是独立的安全机制。
- 多串口设备要逐个配置——别以为配了一个ttyS0,ttyS1就自动好了。我有个项目用了4个串口,每个都得单独配。
- 重启后权限丢失是常态——所以一定要用udev规则或init.rc来持久化配置。
嗯,权限配置这块就这些。下一章我们聊聊怎么在Android应用层打开串口、读写数据。到时候你会看到,前面这些准备工作到底有多重要。