Android 中 Unix Domain Socket(UDS)的使用机制

257 阅读3分钟

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
    # 其他配置...

关键配置参数

参数说明
namesocket 名称(如 logd),对应文件路径为 /dev/socket/logd
type类型:stream(TCP)、dgram(UDP)、seqpacket(有序数据包)
perm文件权限(如 0666 表示读写权限)
user/group所属用户和组(如 logd 用户组,控制访问权限)
seclabelSELinux 安全上下文(可选,默认继承服务的上下文)

生成路径:所有定义的 socket 都会在 /dev/socket/ 目录下生成对应的文件,如 adbdzygote 等。

二、UDS 的创建流程:从 init 到内核的底层实现

1. init 进程解析与创建

init 进程在解析 rc 文件时,通过 DescriptorInfo::CreateAndPublish 函数创建 socket:

  1. 调用 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);
    
  2. 绑定与权限设置

    • 绑定前删除已存在的旧文件(unlink),避免冲突。
    • 通过 lchown 和 fchmodat 设置用户、组和权限(如 logd 用户组,权限 0666)。
  3. 环境变量存储

    • 将 socket 的文件描述符(fd)存入环境变量(如 ANDROID_SOCKET_logd=3),供服务进程获取。

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 通过 zygote socket(stream 类型)接收 SystemServer 的启动请求,孵化应用进程。
  • 日志服务(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 的核心作用

  1. 高效本地通信:作为 Binder 之外的补充,用于 Native 层服务间的高性能通信(如 Zygote、logd)。

  2. 配置驱动:通过 init.rc 统一管理 socket 生命周期,简化系统启动时的资源初始化。

  3. 安全隔离:通过用户组、权限和 SELinux 上下文确保进程间通信的安全性。

类比说明:UDS 在 Android 中如同「系统内部的专用管道」,每个服务通过 init.rc 定义自己的「管道接口」(socket 文件),其他进程通过环境变量找到对应的「管道入口」,实现高效、安全的本地数据传输,避免了网络通信的额外开销。