Android 中 Unix Domain Socket(UDS)的使用机制,解析其在系统配置与代码实现中的关键流程:
一、UDS 在 Android 中的核心配置:init.rc 文件解析
Android 通过 init.rc 脚本 定义系统服务所需的 UDS,以日志服务 logd 为例:
ini
service logd /system/bin/logd
socket logd stream 0666 logd logd # 定义名为 logd 的 socket
# 其他配置...
关键配置参数:
| 参数 | 说明 |
|---|---|
name | socket 名称(如 logd),对应文件路径为 /dev/socket/logd |
type | 类型:stream(TCP)、dgram(UDP)、seqpacket(有序数据包) |
perm | 文件权限(如 0666 表示读写权限) |
user/group | 所属用户和组(如 logd 用户组,控制访问权限) |
seclabel | SELinux 安全上下文(可选,默认继承服务的上下文) |
生成路径:所有定义的 socket 都会在 /dev/socket/ 目录下生成对应的文件,如 adbd、zygote 等。
二、UDS 的创建流程:从 init 到内核的底层实现
1. init 进程解析与创建
init 进程在解析 rc 文件时,通过 DescriptorInfo::CreateAndPublish 函数创建 socket:
-
调用
CreateSocket:- 根据
type确定 socket 类型(SOCK_STREAM/SOCK_DGRAM)。 - 构建
sockaddr_un地址结构,路径为/dev/socket/name(如/dev/socket/logd)。
c
struct sockaddr_un addr; addr.sun_family = AF_UNIX; snprintf(addr.sun_path, sizeof(addr.sun_path), "/%s", name); - 根据
-
绑定与权限设置:
- 绑定前删除已存在的旧文件(
unlink),避免冲突。 - 通过
lchown和fchmodat设置用户、组和权限(如logd用户组,权限0666)。
- 绑定前删除已存在的旧文件(
-
环境变量存储:
- 将 socket 的文件描述符(fd)存入环境变量(如
ANDROID_SOCKET_logd=3),供服务进程获取。
- 将 socket 的文件描述符(fd)存入环境变量(如
2. 关键函数 CreateSocket
c
int CreateSocket(const char* name, int type, bool passcred, mode_t perm, uid_t uid, gid_t gid, const char* socketcon) {
int fd = socket(PF_UNIX, type, 0); // 创建 UDS
bind(fd, &addr, sizeof(addr)); // 绑定路径
lchown(addr.sun_path, uid, gid); // 设置用户组
fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW); // 设置权限
return fd;
}
三、代码中使用 UDS:获取 FD 与通信
1. 通过环境变量获取 FD
Android 提供 android_get_control_socket 函数从环境变量中获取 socket 的 fd:
c
int android_get_control_socket(const char* name) {
// 从环境变量 "ANDROID_SOCKET_<name>" 中获取 fd
int fd = __android_get_control_from_env(ANDROID_SOCKET_ENV_PREFIX, name);
// 验证 socket 路径是否匹配
struct sockaddr_un addr;
getsockname(fd, &addr, sizeof(addr));
return strcmp(addr.sun_path + strlen("/dev/socket/"), name) == 0 ? fd : -1;
}
示例:获取名为 zygote 的 socket fd:
c
int zygoteFd = android_get_control_socket("zygote");
2. 通信示例(以 TCP 为例)
-
服务端(如 Zygote) :
c
int fd = android_get_control_socket("zygote"); listen(fd, 5); // 监听连接 int connFd = accept(fd, &clientAddr, &clientLen); // 接受客户端连接 -
客户端(如 SystemServer) :
c
int fd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr; strcpy(addr.sun_path, "/dev/socket/zygote"); connect(fd, &addr, sizeof(addr)); // 连接服务端 send(fd, "启动请求", sizeof("启动请求"), 0); // 发送数据
四、关键特性与应用场景
1. 权限与安全
- 用户组控制:通过
user/group配置限制访问,如logd服务的 socket 仅允许logd用户组读写。 - SELinux 上下文:通过
seclabel或默认上下文(基于服务可执行文件)确保安全隔离。
2. 系统服务中的实际应用
-
Zygote 与应用进程通信:
- Zygote 通过
zygotesocket(stream类型)接收 SystemServer 的启动请求,孵化应用进程。
- Zygote 通过
-
日志服务(logd) :
logd使用多个 socket 分别处理不同类型的日志数据(如logd用于流式日志,logdr用于有序数据包)。
3. 与 Java 层的交互
Android Java 层通过 LocalSocket 类封装 UDS 操作:
java
// 获取名为 "logd" 的 socket
LocalSocket socket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress("logd", LocalSocketAddress.Namespace.RESERVED);
socket.connect(address); // 连接
OutputStream out = socket.getOutputStream();
out.write("日志数据".getBytes());
五、总结:Android 中 UDS 的核心作用
-
高效本地通信:作为 Binder 之外的补充,用于 Native 层服务间的高性能通信(如 Zygote、logd)。
-
配置驱动:通过 init.rc 统一管理 socket 生命周期,简化系统启动时的资源初始化。
-
安全隔离:通过用户组、权限和 SELinux 上下文确保进程间通信的安全性。
类比说明:UDS 在 Android 中如同「系统内部的专用管道」,每个服务通过 init.rc 定义自己的「管道接口」(socket 文件),其他进程通过环境变量找到对应的「管道入口」,实现高效、安全的本地数据传输,避免了网络通信的额外开销。