前言

本文基于 Android10 ,分析 ZygoteAMS 是如何使用 LocalSocket 建立连接的。

Server

我们先来看服务端,了解 Zygote 的都知道, Zygote 会创建一个 ServerSocket 用来等待 AMS 发起创建新应用的请求,我们就从这里开始看起 /frameworks/base/core/java/com/android/internal/os/Zygote.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";

static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
    int fileDesc;
    // fullSocketName = ANDROID_SOCKET_zygote
    final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;

    try {
        // 从环境变量中读取 ANDROID_SOCKET_zygote
        String env = System.getenv(fullSocketName);
        // 转为int ,也就是文件描述符id
        fileDesc = Integer.parseInt(env);
    } catch (RuntimeException ex) {
        throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);
    }

    try {
        // 创建文件描述符,并用它创建 LocalServerSocket
        FileDescriptor fd = new FileDescriptor();
        // 使用环境变量中读取的值进行赋值
        fd.setInt$(fileDesc);
        return new LocalServerSocket(fd);
    } catch (IOException ex) {
        throw new RuntimeException(
            "Error building socket from file descriptor: " + fileDesc, ex);
    }
}

fullSocketNameANDROID_SOCKET_zygote ,接着从环境变量中读取这个值。然后转为 int 并用它来创建文件描述符。

然后用创建好的文件描述符创建 LocalServerSocket ,到此服务端就准备好了。

Client

服务端创建好之后,我们来看 AMS 是怎么连接上服务端的,我们直接来看客户端的创建,经过一系列跟踪定位到 ZygoteProcess.attemptConnectionToPrimaryZygote 方法 /frameworks/base/core/java/android/os/ZygoteProcess.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 以下内容来自/frameworks/base/core/java/com/android/internal/os/Zygote.java
public static final String PRIMARY_SOCKET_NAME = "zygote";
public static final String SECONDARY_SOCKET_NAME = "zygote_secondary";
// ---------------------

public ZygoteProcess() {
    // 用 zygote 创建 LocalSocketAddress
    mZygoteSocketAddress =
        new LocalSocketAddress(Zygote.PRIMARY_SOCKET_NAME,
                               LocalSocketAddress.Namespace.RESERVED);
    mZygoteSecondarySocketAddress =
        new LocalSocketAddress(Zygote.SECONDARY_SOCKET_NAME,
                               LocalSocketAddress.Namespace.RESERVED);

    mUsapPoolSocketAddress =
        new LocalSocketAddress(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME,
                               LocalSocketAddress.Namespace.RESERVED);
    mUsapPoolSecondarySocketAddress =
        new LocalSocketAddress(Zygote.USAP_POOL_SECONDARY_SOCKET_NAME,
                               LocalSocketAddress.Namespace.RESERVED);
}

private void attemptConnectionToPrimaryZygote() throws IOException {
    if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
        primaryZygoteState =
            ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress);

        maybeSetApiBlacklistExemptions(primaryZygoteState, false);
        maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
        maybeSetHiddenApiAccessStatslogSampleRate(primaryZygoteState);
    }
}

mZygoteSocketAddress 是在构造方法中创建的,它使用字符串 zygote 创建 LocalSocketAddress ,紧接着调用了 ZygoteState.connect

/frameworks/base/core/java/android/os/ZygoteProcess.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
static ZygoteState connect(@NonNull LocalSocketAddress zygoteSocketAddress,
                           @Nullable LocalSocketAddress usapSocketAddress)
    throws IOException {

    DataInputStream zygoteInputStream;
    BufferedWriter zygoteOutputWriter;
    // 创建客户端 LocalSocket
    final LocalSocket zygoteSessionSocket = new LocalSocket();

    // 使用上一步创建的 LocalSocketAddress 连接服务端
    zygoteSessionSocket.connect(zygoteSocketAddress);
    zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
    zygoteOutputWriter =
        new BufferedWriter(
            new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
            Zygote.SOCKET_BUFFER_SIZE);

    return new ZygoteState(zygoteSocketAddress, usapSocketAddress,
                                   zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                                   getAbiList(zygoteOutputWriter, zygoteInputStream));
}

在这里创建了客户端 LocalSocket ,然后调用 connect 和上一步创建的 LocalSocketAddress 进行连接。

在服务端创建的过程中,服务端使用的是文件描述符来创建 LocalServerSocket 的,而在客户端使用的是 LocalSocketAddress ,并且用的是字符串 zygote 来初始化。

那么问题来了,文件描述符是怎么和 LocalSocketAddress("zygote") 关联上的?

服务端和客户端是怎么关联上的

起初我以为答案在 LocalSocketAddress 中,一直追踪源码,发现 LocalSocketAddress 里面就只是保存了 namenamespace 而已,什么也没做。

所以我就猜测答案可能在 connect

/frameworks/base/core/java/android/net/LocalSocket.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public void connect(LocalSocketAddress endpoint) throws IOException {
    synchronized (this) {
        if (isConnected) {
            throw new IOException("already connected");
        }

        implCreateIfNeeded();
        impl.connect(endpoint, 0);
        isConnected = true;
        isBound = true;
    }
}

implCreateIfNeeded 中创建了 LocalSocketImpl 然后把连接任务交给了它。 /frameworks/base/core/java/android/net/LocalSocketImpl.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private native void connectLocal(FileDescriptor fd, String name,
            int namespace) throws IOException;

protected void connect(LocalSocketAddress address, int timeout)
                        throws IOException {
    if (fd == null) {
        throw new IOException("socket not created");
    }

    connectLocal(fd, address.getName(), address.getNamespace().getId());
}

发现 connect 去调用 connectLocal ,它是个本地方法,也没发现它们有什么关联。不过这里却出现了 FileDescriptor ,只要找到它在哪里赋值的,答案是不是就可以揭晓了?

我怀着激动的心情找到 fd 赋值的地方,发现有两处地方,如下所示 /frameworks/base/core/java/android/net/LocalSocketImpl.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
LocalSocketImpl(FileDescriptor fd)
{
    this.fd = fd;
}

public void create(int sockType) throws IOException {
    if (fd != null) {
        throw new IOException("LocalSocketImpl already has an fd");
    }

    int osType;
    switch (sockType) {
    case LocalSocket.SOCKET_DGRAM:
        osType = OsConstants.SOCK_DGRAM;
        break;
    case LocalSocket.SOCKET_STREAM:
        osType = OsConstants.SOCK_STREAM;
        break;
    case LocalSocket.SOCKET_SEQPACKET:
        osType = OsConstants.SOCK_SEQPACKET;
        break;
    default:
        throw new IllegalStateException("unknown sockType");
    }
    try {
        fd = Os.socket(OsConstants.AF_UNIX, osType, 0);
        mFdCreatedInternally = true;
    } catch (ErrnoException e) {
        e.rethrowAsIOException();
    }
}

第一处在构造方法中,显然不是我们要的,因为客户端使用的是字符串创建的 LocalSocketAddress

剩下就只有 create 方法了,在这个方法中使用 Os.socket 创建了 fd ,这是客户端的描述符,也没有和字符串 zygote 关联上,回顾一下服务端使用的是环境变量中的值来创建的 FileDescriptor ,这里的参数列表是 FileDescriptor socket (int domain, int type, int protocol) ,和 zygote 没关系,线索到这突然就断了。

既然客户端看不出 zygote 是怎么和 FileDescriptor 关联上的,我们就从服务端入手,看看能不能找到答案。

经过一通搜索和排查,发现和客户端一样,就是没有找到 FileDescriptorzygote 关联上的地方。

回到 Socket 创建的地方

在服务端和客户端都没有找到答案,不过我在服务端发现一个有意思的地方,一直被我忽略了。就是在服务端创建 FileDescriptor 的时候从环境变量获取到 ANDROID_SOCKET_zygote 的值。

1
2
3
4
5
6
7
8
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
// 从环境变量中读取 ANDROID_SOCKET_zygote
String env = System.getenv(fullSocketName);
// 转为int ,也就是文件描述符id
fileDesc = Integer.parseInt(env);
// 创建文件描述符,并用它创建 LocalServerSocket
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);

看来 ANDROID_SOCKET_zygote 这个环境变量是突破的关键,但是它是在哪里赋值的呢?

init 进程

我们回到 init 进程中看 Zygote 进程都有哪些配置 /system/core/rootdir/init.zygote64.rc

1
2
3
4
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    ...

目光放到最后一行,这里配置了创建一个名为 zygotesocket ,原来 Zygote 中的 Socket 并不是 Zygote 创建的,而是 init 进程创建的。

这样就能说的通了,客户端创建 LocalSocketAddress 使用 zygote 作为参数,就能连到这个 socket 上。

那么服务端获取的 ANDROID_SOCKET_zygote 的值又是怎么告诉 Zygote 进程的呢?从上面的分析来看,通过环境变量来实现的。

环境变量的赋值

/system/core/init/service.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
template <typename T>
Result<Success> Service::AddDescriptor(std::vector<std::string>&& args) {
    int perm = args.size() > 3 ? std::strtoul(args[3].c_str(), 0, 8) : -1;
    Result<uid_t> uid = 0;
    Result<gid_t> gid = 0;
    std::string context = args.size() > 6 ? args[6] : "";

    // ...

    auto descriptor = std::make_unique<T>(args[1], args[2], *uid, *gid, perm, context);

    auto old =
        std::find_if(descriptors_.begin(), descriptors_.end(),
                     [&descriptor] (const auto& other) { return descriptor.get() == other.get(); });

    if (old != descriptors_.end()) {
        return Error() << "duplicate descriptor " << args[1] << " " << args[2];
    }

    descriptors_.emplace_back(std::move(descriptor));
    return Success();
}

// 判断 Socket 符不符合条件
Result<Success> Service::ParseSocket(std::vector<std::string>&& args) {
    if (!StartsWith(args[2], "dgram") && !StartsWith(args[2], "stream") &&
        !StartsWith(args[2], "seqpacket")) {
        return Error() << "socket type must be 'dgram', 'stream' or 'seqpacket'";
    }
    // 添加到列表中
    return AddDescriptor<SocketInfo>(std::move(args));
}

Result<Success> Service::Start() {

    pid_t pid = -1;
    if (namespace_flags_) {
        pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

    if (pid == 0) {
        umask(077);

        if (auto result = EnterNamespaces(); !result) {
            LOG(FATAL) << "Service '" << name_ << "' could not enter namespaces: " << result.error();
        }

        // 遍历每个 descriptor 调用 CreateAndPublish 方法
        std::for_each(descriptors_.begin(), descriptors_.end(),
                      std::bind(&DescriptorInfo::CreateAndPublish, std::placeholders::_1, scon));

        _exit(127);
    }
    return Success();
}

init 进程在解析 /system/core/rootdir/init.zygote64.rc 文件时,解析 socket 会调用 ParseSocket ,在 ParseSocket 中会调用 AddDescriptor 把解析的参数保存到 descriptors ,然后在 start 方法中遍历 descriptors ,调用 DescriptorInfo::CreateAndPublish 方法创建 Socket 并且设置环境变量。

CreateAndPublish

/system/core/init/descriptors.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 以下两个宏定义来自 /system/core/libcutils/include/cutils/sockets.h
#define ANDROID_SOCKET_ENV_PREFIX	"ANDROID_SOCKET_"
#define ANDROID_SOCKET_DIR		"/dev/socket"
// ---------------------------------------

DescriptorInfo::DescriptorInfo(const std::string& name, const std::string& type, uid_t uid,
                               gid_t gid, int perm, const std::string& context)
        : name_(name), type_(type), uid_(uid), gid_(gid), perm_(perm), context_(context) {
}

const std::string SocketInfo::key() const {
    return ANDROID_SOCKET_ENV_PREFIX;
}

void DescriptorInfo::CreateAndPublish(const std::string& globalContext) const {
    const std::string& contextStr = context_.empty() ? globalContext : context_;
    // 创建 Socket
    int fd = Create(contextStr);
    if (fd < 0) return;

    // key 就是 ANDROID_SOCKET_zygote, name 就是 init.rc 中配置的 zygote
    std::string publishedName = key() + name_;
    std::for_each(publishedName.begin(), publishedName.end(),
                  [] (char& c) { c = isalnum(c) ? c : '_'; });

    // 把 fd 转成字符串
    std::string val = std::to_string(fd);
    // 设置环境变量
    setenv(publishedName.c_str(), val.c_str(), 1);

    fcntl(fd, F_SETFD, 0);
}

CreateAndPublish 中先创建了 socket 然后调用 setenv 设置环境变量。至此我们就可以明白了 AMS 中的客户端是怎么连上 Zygote 中的服务端了。

根据这个发现,环境变量其实也能够用于进程间通信。

总结

  1. zygote.rc 文件中配置了需要创建一个名为 zygotesocket
  2. init 进程在解析 zygote.rc 文件时会根据配置中的参数创建一个 socket 并把对应的描述符通过环境变量的方式公布出去。
  3. Zygote 进程会从环境变量中读取 init 进程创建 socket 时所设置的环境变量,用于创建 LocalServerSocket
  4. AMS 在发起创建应用程序的请求时,会使用 zygote 这个字符串创建 LocalSocket
  5. 由于服务端使用到的文件描述符的 id 和客户端使用到 zygote 表示的是同一个 socket ,所以客户端之间能够相互通通信。

参考