前言
本文基于 Android10
,分析 Zygote
与 AMS
是如何使用 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);
}
}
|
fullSocketName
是 ANDROID_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
里面就只是保存了 name
和 namespace
而已,什么也没做。
所以我就猜测答案可能在 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
关联上的,我们就从服务端入手,看看能不能找到答案。
经过一通搜索和排查,发现和客户端一样,就是没有找到 FileDescriptor
和 zygote
关联上的地方。
回到 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
...
|
目光放到最后一行,这里配置了创建一个名为 zygote
的 socket
,原来 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
中的服务端了。
根据这个发现,环境变量其实也能够用于进程间通信。
总结
zygote.rc
文件中配置了需要创建一个名为 zygote
的 socket
init
进程在解析 zygote.rc
文件时会根据配置中的参数创建一个 socket
并把对应的描述符通过环境变量的方式公布出去。
Zygote
进程会从环境变量中读取 init
进程创建 socket
时所设置的环境变量,用于创建 LocalServerSocket
AMS
在发起创建应用程序的请求时,会使用 zygote
这个字符串创建 LocalSocket
- 由于服务端使用到的文件描述符的
id
和客户端使用到 zygote
表示的是同一个 socket
,所以客户端之间能够相互通通信。
参考