大佬就是强!意外收获字节跳动内部资料——《Android-Binder机制实现原理》

新的开始

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

《系列学习视频》

《系列学习文档》

《我的大厂面试之旅》

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

最后是 mmap 函数,它用来实现内存映射。使用的时候需要引入头文件 #include <sys/mman.h>. 与之对应的还有 munmap 函数。它们的函数定义如下,

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);

这里的参数的含义是:

  1. start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址;
  2. length:映射区的长度。长度单位是以字节为单位,不足一内存页按一内存页处理;
  3. prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过 o r运算合理地组合在一起;
  4. flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体;
  5. fd:有效的文件描述词。一般是由 open() 函数返回,其值也可以设置为-1,此时需要指定 flags 参数中的 MAP_ANON,表明进行的是匿名映射;
  6. off_toffset:被映射对象内容的起点。

成功执行时,mmap() 返回被映射区的指针,munmap() 返回0。失败时,mmap() 返回 MAP_FAILED[其值为(void *)-1],munmap() 返回 -1.

4.3 ServiceManger 启动

Binder 中的 ServiceManager 并非 Java 层的 ServiceManager,而是 Native 层的。启动 ServiceManager 由 init 进程通过解析 init.rc 文件而创建。启动的时候会找到上述源码目录中的 service_manager.c 文件中,并调用它的 main() 方法,

// platform/framework/native/cmds/servicemanager.c
int main(int argc, char** argv)
{
struct binder_state *bs;
char *driver;

if (argc > 1) {
driver = argv[1];
} else {
driver = “/dev/binder”;
}
// 1. 打开 binder 驱动
bs = binder_open(driver, 128*1024);
// …
// 2. 将当前的 ServiceManger 设置成上下文
if (binder_become_context_manager(bs)) {
return -1;
}
// …
// 3. 启动 binder 循环,进入不断监听状态
binder_loop(bs, svcmgr_handler);
return 0;
}

ServcieManager 启动的过程就是上面三个步骤,无需过多说明。下面我们给出这三个方法具体实现的。在下面的代码中你将看到我们之前介绍的三个函数的实际应用。相应有了前面的铺垫之后你理解起来不成问题 😃

// platform/framework/native/cmds/servicemanager.c
struct binder_state binder_open(const char driver, size_t mapsize)
{
struct binder_state *bs;
struct binder_version vers;
bs = malloc(sizeof(*bs));
if (!bs) {
errno = ENOMEM;
return NULL;
}
// 打开设备驱动
bs->fd = open(driver, O_RDWR | O_CLOEXEC);
if (bs->fd < 0) {
goto fail_open;
}
// 向驱动发送指令,获取binder版本信息
if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
(vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
goto fail_open;
}
bs->mapsize = mapsize;
// 通过系统调用,mmap 内存映射,mmap 必须是 page 的整数倍
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
if (bs->mapped == MAP_FAILED) {
goto fail_map;
}
return bs;
fail_map:
close(bs->fd);
fail_open:
free(bs);
return NULL;
}

在上面的代码中,先使用 open() 函数打开设备驱动(就是一个打开文件的操作),然后使用 ioctl() 函数向上面的设备驱动发送指令以获取设备信息。最后,通过 mmap() 函数实现内存映射,并将上述的文件描述符传入。这里的 binder_state 是一个结构体,定义如下。其实就是用来描述 binder 的状态。从上面我们也能看到它的三个变量的赋值过程。

// platform/framework/native/cmds/servicemanager.c
struct binder_state
{
int fd;
void *mapped;
size_t mapsize;
};

当然,在上面的代码中,我们又见到了久违的 goto 指令。它们主要用来处理发生一些异常的情况。

打开了驱动之后,注册为上下文的方法更加简单,

// platform/framework/native/cmds/servicemanager.c
int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
复制代码

就是一个 ioctl 函数,使用指令 BINDER_SET_CONTEXT_MGR 将当前的 ServiceManager 注册为上下文。

最后就是启动 Binder 循环了。它的逻辑也没有想象中得复杂,就是启动了 for 循环,

// platform/framework/native/cmds/servicemanager.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
int res;
struct binder_write_read bwr;
uint32_t readbuf[32];

bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;

readbuf[0] = BC_ENTER_LOOPER;
// 将 BC_ENTER_LOOPER 命令发送给 binder 驱动,内部调用 ioctl 函数
binder_write(bs, readbuf, sizeof(uint32_t));

for (;😉 {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
// 使用 iotcl 函数读取
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
break;
}
// 解析
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
if (res == 0) {
break;
}
if (res < 0) {
break;
}
}
}

从上面看出,函数将会在 binder_write() 中将命令发送给 Binder 驱动,以启动循环。其实内部也是调用 ioctl 函数实现的。然后程序会启动一个循环来不断读取、解析。这是服务器很典型的操作了。

当然,我们上面分析的是 ServiceManager 中向 Binder 写命令的过程,而驱动如何解析呢?当然是在驱动中实现了,详细的过程可以查看 Binder 驱动部分的源码。

4.4 Binder 的跨进程通信过程

下面我们以 AMS 作为例子来讲解下 Binder 跨进程通信的实现过程。首先,当我们调用 startActivity() 方法的时候,最终将会进入 ActivityManager 以获取 AMS,

// platform/framework/base/core/java/android/app/ActivityManager.java
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;

这里会使用 ServiceManger 来按名称查找 AMS,查找到 Binder 对象之后将其转换成 AMS 就可以使用了。之前,我们也说过用来查找 AMS 的 SeerviceManager 本身也是一种服务。所以,它这里的方法也是通过 Binder 来实现的。那么,我们就从这里的 getService() 方法入手。

// platform/framework/base/core/java/android/os/ServiceManager.java
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(rawGetService(name));
}
} catch (RemoteException e) { /* … */ }
return null;
}

这里会先尝试从缓存当中取 Binder,取不到的话就从远程进行获取。这里使用 rawGetService() 方法来从远程获取 Binder,代码如下,

// platform/framework/base/core/java/android/os/ServiceManager.java
private static IBinder rawGetService(String name) throws RemoteException {
final IBinder binder = getIServiceManager().getService(name);
// …
return binder;
}

// platform/framework/base/core/java/android/os/ServiceManager.java
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
sServiceManager = ServiceManagerNative
.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
return sServiceManager;
}

rawGetService() 方法中会使用 ServiceManagerNativegetService() 方法从远程获取 Binder. 这里的 ServiceManagerNative 本质上只是一个代理类,它实际的逻辑是由 BinderInternal.getContextObject() 返回的 Binder 实现的。

也许你已经晕了,怎么那么多 Binder……我来说明下。当要查找 AMS 的时候实际上是一个跨进程的调用过程,也就是实际的查找的逻辑是在另一个进程实现,因此需要 Binder 来通信。而查找 AMS 的远程对象实际上就是我们上面所说的 ServiceManager (Native 层的而不是 Java 层的,Java 层的 ServiceManager 是一个代理类,是用来从远程获取服务的)。

因此,按照上面的描述,BinderInternal.getContextObject() 返回的就应该是远程的 Binder 对象。于是方法进入 Native 层,

// platform/framework/base/core/jni/android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
sp b = ProcessState::self()->getContextObject(NULL);
return javaObjectForIBinder(env, b);
}

这里的 ProcessState::self() 是否熟悉呢?你是否还记得在上一篇文章中,我们介绍 Android 系统启动过程的时候介绍过它。我们曾经使用它来开启 Binder 的线程池。这里的 self() 方法其实是用来获取一个单例对象的。我们可以直接由 getContextObject() 进入 getStrongProxyForHandle() 方法。从下面的方法中我们可以看出,这里调用了 BpBindercreate() 方法创建了一个 BpBinder 实例并返回,也就是我们的 ServiceManager.

// plaftorm/framework/native/libs/binder/ProcessState.cpp
sp ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp result;
AutoMutex _l(mLock);
handle_entry* e = lookupHandleLocked(handle);
if (e != nullptr) {
IBinder* b = e->binder;
if (b == nullptr || !e->refs->attemptIncWeak(this)) {
// …
// 调用 BpBinder
b = BpBinder::create(handle);
e->binder = b;
if (b) e->refs = b->getWeakRefs();
result = b;
} else {
result.force_set(b);
e->refs->decWeak(this);
}
}

当我们拿到了 ServiceManager 的 Binder 之后就可以调用它的 getService() 方法来获取服务了,

// platform/framework/base/core/java/android/os/ServiceManagerNative.java
public IBinder getService(String name) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}

这里的 mRemote 就是之前返回的 BpBinder,这里调用它的 transact() 方法,并传入了一个方法标记 GET_SERVICE_TRANSACTION.

// platform/framework/native/libs/binder/BpBinder.cpp
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
if (mAlive) {
status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}

显然这里会调用 IPCThreadState 的 self() 方法先获取一个单例的对象,然后调用它的 transact() 方法继续方法的执行。

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle, uint32_t code,
const Parcel& data, Parcel* reply, uint32_t flags)
{
status_t err;
// …
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
// …
if ((flags & TF_ONE_WAY) == 0) { // OneWay 类型的调用,同步的
// …
if (reply) {
// 等待相应
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
IF_LOG_TRANSACTIONS() {
TextOutput::Bundle _b(alog);
if (reply) alog << indent << *reply << dedent << endl;
else alog << “(none requested)” << endl;
}
} else { // 异步的
err = waitForResponse(nullptr, nullptr);
}
return err;
}

上面会调用 writeTransactionData() 方法用来将数据写入到 Parcel 中。然后将会进入 waitForResponse() 方法处理与 ServiceManager 交互的结果。而真实的交互发生的地方位于 talkWithDriver() 方法,

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
if (mProcess->mDriverFD <= 0) {
return -EBADF;
}

binder_write_read bwr;
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();

if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}

if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
// 通过 ioctl 读写操作,与 Binder Driver 进行交互
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
if (mProcess->mDriverFD <= 0) {
err = -EBADF;
}
} while (err == -EINTR);
// …
return err;
}

binder_write_read 结构体用来与 Binder 设备交换数据的结构, 通过 ioctl 与 mDriverFD 通信,是真正与 Binder 驱动进行数据读写交互的过程。先向service manager进程发送查询服务的请求(BR_TRANSACTION)。然后,service manager 会在之前开启的循环中监听到,并使用 svcmgr_handler() 方法进行处理。

// platform/framework/native/cmds/servicemanager.c
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
// …
switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
if (!handle)
break;
bio_put_ref(reply, handle);
return 0;
case SVC_MGR_ADD_SERVICE: // …
case SVC_MGR_LIST_SERVICES: // …
}
return 0;
}

显然,这里会从 binder_transaction_data 中取出 code,即 SVC_MGR_GET_SERVICE,然后使用 do_find_service() 方法查找服务。然后再 binder_send_reply() 应答发起者将结果返回即可。

4.5 Binder 高效通信的原因

上面我们梳理了 Binder 通信的过程,从上面我们似乎并没有看到能证明 Binder 高效的证据。那么 Binder 究竟靠什么实现高效的呢?

实际上,Binder 之所以高效,从我们上面的代码还真看不出来。因为,我们上面的代码并没有涉及 Binder 驱动部分。正如我们之前描述的那样,ServiceManager、客户端和服务器实际是靠 Binder 驱动这个中间媒介进行交互的。而 Binder 高效的地方就发生在 Binder 驱动部分。

就像图片描述的那样,当两个进程之间需要通信的时候,Binder 驱动会在两个进程之间建立两个映射关系:内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系。这样,当把数据从 1 个用户空间拷贝到内核缓冲区的时候,就相当于拷贝到了另一个用户空间中。这样只需要做一次拷贝,省去了内核中暂存这个步骤,提升了一倍的性能。实现内存映射靠的就是上面的 mmap() 函数。

4、Binder 的使用

4.1 代理模式

Binder 本质上只是一种底层通信方式,和具体服务没有关系。为了提供具体服务,Server 必须提供一套接口函数以便 Client 通过远程访问使用各种服务。这时通常采用代理设计模式:将接口函数定义在一个抽象类中,ServerClient 都会以该抽象类为基类实现所有接口函数。所不同的是 Server 端是真正的功能实现,而 Client 端是对这些函数远程调用请求的包装。为了简化这种设计模式,Android 中提供了 AIDL 供我们使用。下文中我们会介绍 AIDL 相关的内容以及它的一些基本的使用方式。

4.2 AIDL

AIDL (Android Interface Definition Language,Android 接口定义语言) 是一种文件格式,用来简化 Binder 的使用。当使用 Binder 的时候,只需要创建一个后缀名为 .aidl 的文件,然后像定义接口一样定义方法。定义完毕之后,使用工具 aidl.exe 即可生成 Binder 所需要的各种文件。当然,我们的 AS 已经为我们集成了 aidl.exe,所以,只需要在定义了 AIDL 文件之后,编译即可生成使用 Binder 时所需的文件。当然,不使用 AIDL,直接编写 Binder 所需的 java 文件也是可以的。

AIDL 是一种接口定义语言,它与 Java 中定义接口的方式有所区别。下面我们通过一个例子来说明 AIDL 的使用方式。

这里我们模拟一个笔记管理的类,通过在 Activity 中与一个远程的 Service 进行交互来实现 IPC 的效果。这里,我们先要定义数据实体 Note,它只包含两个字段,并且实现了 Parcelable。这里 Note 所在的目录是 me.shouheng.advanced.aidl,然后,我们需要在 src/main 建立一个同样的包路径,然后定义所需的 AIDL 文件:

// INoteManager.aidl
package me.shouheng.advanced.aidl;
import me.shouheng.advanced.aidl.Note;
interface INoteManager {
Note getNote(long id);
void addNote(long id, String name);
}

// Note.aidl
package me.shouheng.advanced.aidl;
parcelable Note;

注意,在 INoteManager 文件中,我们定义了远程服务所需的各种方法。这里只定义了两个方法,一个用来获取指定 id 的笔记,一个用来向远程服务中添加一条笔记记录。

这样定义完了之后,我们可以对项目进行编译,这样就可以 build 目录下面得到为我们生成好的 INoteManager 类文件。以后,我们就可以使用这个文件中生成类和方法来进行远程通信。但在使用该接口之前,我们还是先来看一下其中都生成了些什么东西:

package me.shouheng.advanced.aidl;

public interface INoteManager extends android.os.IInterface {

// 交给远程来实现具体的业务逻辑
public static abstract class Stub extends android.os.Binder implements me.shouheng.advanced.aidl.INoteManager {

private static final java.lang.String DESCRIPTOR = “me.shouheng.advanced.aidl.INoteManager”;

public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

// 使用代理包装远程对象
public static me.shouheng.advanced.aidl.INoteManager asInterface(android.os.IBinder obj) {
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof me.shouheng.advanced.aidl.INoteManager))) {
return ((me.shouheng.advanced.aidl.INoteManager)iin);
}
// 返回代理对象
return new me.shouheng.advanced.aidl.INoteManager.Stub.Proxy(obj);
}

@Override
public android.os.IBinder asBinder() {
return this;
}

// 真实地发送数据交换的地方
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getNote: {
data.enforceInterface(DESCRIPTOR);
long _arg0;
_arg0 = data.readLong();
// 使用模板方法来实现业务
me.shouheng.advanced.aidl.Note _result = this.getNote(_arg0);
reply.writeNoException();
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;

最后

一线互联网Android面试题含详解(初级到高级专题)

这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率

如果设置门槛,很多开发者朋友会因此错过这套高级架构资料,错过提升成为架构师的可能。这就失去了我们的初衷;让更多人都能通过高效高质量的学习,提升自己的技术和格局,升职加薪。

最后送给大家一句话,望共勉,永远不要放弃自己的梦想和追求;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

含详解(初级到高级专题)**

这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率

[外链图片转存中…(img-tYXhGZKJ-1715322049235)]

如果设置门槛,很多开发者朋友会因此错过这套高级架构资料,错过提升成为架构师的可能。这就失去了我们的初衷;让更多人都能通过高效高质量的学习,提升自己的技术和格局,升职加薪。

最后送给大家一句话,望共勉,永远不要放弃自己的梦想和追求;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值