深入Android系统(三)Binder-3-原理
Binder的实现原理
涉及到原理源码肯定是少不了的,9.0 binder
相关的源码分为三部分:
- Java:
frameworks/base/core/java/android/os/Binder.java
- native:
frameworks/native/libs/binder/
- driver:
common/drivers/android/binder.c
还有一点需要明确的是:
-
用户进程
:针对内核
空间或者binder驱动
来说的,这里指的是向binder驱动
发送消息的进程 -
客户进程
:针对binder通信
的服务进程
来说的,这里指的是发起binder调用
的进程
书中这两个名词没有做详细说明,一开始看的有点晕
源码在手,干啥都有
Binder设计相关的问题
Binder
实现的远程调用是一种面向对象
的远程调用,那么它和面向过程
的远程调用区别在什么地方呢?
-
面向过程
的远程调用实现起来比较容易:- 只需要通过某种方式把需要执行的
函数号
和参数
传递到服务进程
- 然后
服务进程
根据函数号
和参数
执行对应的函数就完成了
- 只需要通过某种方式把需要执行的
-
面向对象
的调用则比较复杂:- 同一个服务类可以创建对个对象
- 调用时不但要通过
函数号
和参数
来识别要执行函数 - 同时还要指定具体的对象
- 调用时不但要通过
- 对象是有生命周期的,需要监听管理
- 服务中的
实体对象
死亡后,客户进程的引用对象
也需要删除 - 这个过程需要自动完成而不能由上层的客户程序协助完成
- 因此为了管理方便,客户进程中需要(
ProcessState
类的作用):- 集中管理本进程中所有的
Binder引用对象
- 并负责它们的创建和释放
- 集中管理本进程中所有的
- 服务中的
- 同一个服务类可以创建对个对象
设计复杂也带来了功能的强大,正因为Binder
是面向对象
的,我们可以创建多个Binder实体对象
来服务不同的客户,每个对象有自己的数据。相互之间不会干扰。
为了系统中所有引用对象
和实体对象
能相互关联:
-
Binder
在驱动中建立了一张所有进程的引用对象
和实体对象
的关联表
。 - 有了关联,
Binder
又必须保证用户进程中实体对象
和引用对象
跟驱动中的数据一致- 为了达到这个目标,
Binder
定义了自己的引用计数规则,而且这种规则是跨进程的。
- 为了达到这个目标,
参数的传递问题:
- 一般的对象作为参数传递没有太大问题,只需要
序列化
和反序列化
就能实现。 - 但是
Binder对象
作为参数传递的时候,就会面临实体对象
和引用对象
相互转换的问题。- 为了让上层应用使用方便,这种转换在驱动中自动完成
- 普通的
IPC
传递参数数据时,要经历两次数据复制的过程:- 一次是从调用者的数据缓冲区复制到内核的缓冲区
- 一次是从内核的缓冲区复制到接受进程的读缓冲区
-
Binder
为了提高效率:- 为每一个进程创建了一块缓存区,这块缓存区在内核和用户进程间共享
- 传输数据到驱动,需要:
- 从发送进程的用户空间缓存区复制到目标进程在驱动的缓存区
- 目标进程从驱动中读取数据就不需要从内核空间复制到用户空间了,而是直接从内核共享的缓存区中读取
- 这样减少了一次数据复制的过程
对于服务进程中Binder调用
的执行:
- 每次执行必须在一个线程中完成
- 如果线程不停地创建和释放,会带来很大的系统开销。
- 使用
线程池
来管理Binder调用
的执行- 在
Binder
的设计中,除了第一个线程是应用层主动创建的 - 线程池中的其他线程都是在驱动的请求下才创建的
- 这样将线程数量降到最低,并保证从驱动到来的
Binder调用
有线程可以使用。
- 在
Binder的线程模型
Binder 的线程池
在Zygote进程
启动时,会调用AppRuntime
的onZygoteInit
函数(书中第8章,还没看到),代码如下:
virtual void onZygoteInit()
{
sp<ProcessState> proc = ProcessState::self();
ALOGV("App process: starting thread pool.\n");
proc->startThreadPool();
}
所有Android应用都是从Zygote进程
中fork
出来的。因此,这段代码对所有应用进程都有效。
-
onZygoteInit
函数首先调用self
函数来得到ProcessState
类的实例,每个进程都只会有一个实例。ProcessState
构造函数如下:ProcessState::ProcessState(const char *driver) : mDriverName(String8(driver)) , mDriverFD(open_driver(driver)) //...... { if (mDriverFD >= 0) { // mmap the binder, providing a chunk of virtual > address space to receive transactions. mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); if (mVMStart == MAP_FAILED) { // *sigh* ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str()); close(mDriverFD); mDriverFD = -1; mDriverName.clear(); } } }
-
ProcessState
类做了两件事:- 调用
open_driver()
函数打开Binder设备
- 调用
mmap()
函数在驱动中分配了一块内存空间- 这块内存空间大小略小于1MB,定义如下:
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
- 这块内存并不是给上层应用使用
- 这块内存用于Binder驱动中接受传递给本进程的Binder数据
- 这块内存在内核和本应用*享
- 调用
- 得到
ProcessState
类的实例后,调用它的startThreadPool()
函数启动线程池
,看下代码:void ProcessState::startThreadPool(){ AutoMutex _l(mLock); if (!mThreadPoolStarted) { mThreadPoolStarted = true; spawnPooledThread(true); } }
- 先判断
mThreadPoolStarted
是否为true
,不为true
才继续执行 - 然后把
mThreadPoolStarted
设置为true
,这说明startThreadPool()
在进程中只会运行一次 - 接着调用
spawnPooledThread
函数,参数为true
void ProcessState::spawnPooledThread(bool isMain) { if (mThreadPoolStarted) { String8 name = makeBinderThreadName(); ALOGV("Spawning new pooled thread, name=%s\n", name.string()); sp<Thread> t = new PoolThread(isMain); t->run(name.string()); } }
-
spawnPooledThread
函数的作用是创建加入线程池的线程 - 函数中创建了一个
PoolThread
类,类的run
函数会创建线程 - 传入的参数为
true
,说明这个线程是线程池
的第一个线程 - 以后再创建的线程都是接到驱动通知后创建的,传入的参数为
false
,像这样status_t IPCThreadState::executeCommand(int32_t cmd){ //...... case BR_SPAWN_LOOPER: mProcess->spawnPooledThread(false); break; //...... }
- 我们再看下
PoolThread
类的业务实现threadLoop
函数protected: virtual bool threadLoop() { IPCThreadState::self()->joinThreadPool(mIsMain); return false; }
- 返回
false
代表执行一次,为什么是在threadLoop()
中执行业务逻辑,可以看下Android 中的threadLoop
- 具体调用细节大家阅读
frameworks
源码吧,路径应该是在:frameworks/av/services/audioflinger/Threads.h
-
threadLoop
函数只是调用了IPCThreadState
的joinThreadPool
函数,这个函数后面单练它
- 返回
- 先判断
好的,我们先来梳理下线程池这部分内容:
- 首先,应用启动时会执行
onZygoteInit
函数,这部分会打开Binder设备
并申请共享内存空间
- 然后,执行
ProcessState
的startThreadPool
创建线程池
- 然后,通过创建
PoolThread
的实例,创建线程池
中的第一个线程 - 最后,
PoolThread
只是简单调用了IPCThreadState
的joinThreadPool
函数
关于IPCThreadState
,稍后详谈
调用Binder服务的线程
客户端Binder服务的调用是通过IBinder的transact函数完成的。这里的IBInder实际上是BpBinder对象,代码如下:
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// Once a binder has died, it will never come back to life.
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
静态函数获取IPCThreadState
的指针-
IPCThreadState
对象会和每个线程关联 -
self
函数会判断本线程是否有关联的IPCThreadState
对象 - 没有关联的对象则新创建一个
IPCThreadState
对象,并保存到当前线程中
-
- 得到
IPCThreadState
对象后,接着调用了IPCThreadState
的transact
,我们看下代码:
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
status_t err;
flags |= TF_ACCEPT_FDS;
//......
//把要发送的数据放到类的成员变量mOut中
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
if (err != NO_ERROR) {
if (reply) reply->setError(err);
return (mLastError = err);
}
if ((flags & TF_ONE_WAY) == 0) {// 同步调用方式
//......
if (reply) {
// 调用者要求返回结果,此时向底层发送数据并等待返回值
err = waitForResponse(reply);
} else {
// 调用者不需要返回值,但是还要等待远程执行完毕
// 这里用fakeRely来接收返回的Parcel对象
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
//......
} else {// 异步调用方式,函数会立即返回
err = waitForResponse(NULL, NULL);
}
return err;
}
-
IPCThreadState
的transact
首先执行writeTransactionData
包要传输的数据保存到mOut
变量中 - 然后通过
waitForResponse
把mOut
中的数据通过ioctl
发送给底层驱动
对于一个进程而言,只有一个Binder驱动
的文件描述符
,所有ioctl
调用都是使用这个描述符。如果客户端
有好几个线程同时执行远程调用,它们都将在同一个描述符的ioctl
函数上等待。那么当数据到来时,哪个线程会受到回复呢?
- 通常的
IO模型
,在同一个描述符上等待的多个线程会被随机唤醒一个 - 但是Android在
Binder驱动
中记录了每次Binder调用的信息,其中就包括线程ID
,因此Binder驱动
知道返回值应该交给哪个线程- 由驱动来处理线程的唤醒比在应用层做同样的事情要更简单,效率也更高
- 但是,从架构的角度来看,这种设计比较糟糕,应用层和驱动产生了耦合。
好的,我们在对这部分做个小结:
-
客户端
从某个线程中发起调用,将参数打包后,通过ioctl
函数传递给驱动 -
客户端
挂起并等待ioctl
返回函数的结果 -
binder驱动
记录下调用线程的信息,然后根据调用的binder对象
寻找Binder服务
所在的进程,也就是服务端
-
binder驱动
找到服务端
后先查看是否有空闲线程,没有则通知服务端
创建 -
服务端
得到空闲线程后,根据binder驱动
中保存的BBinder对象
的指针调用相应的函数 -
服务端
在函数返回后在通过ioctl
把结果打包传递给binder驱动
-
binder驱动
根据返回信息查找调用者线程 -
binder驱动
找到调用的线程后并唤醒它,并通过ioctl
函数把结果传递回去 -
客户端
的线程得到返回结果后继续运行
Binder对象的传递
当Binder对象
作为参数传递是,会有两种情形:
-
Binder实体对象
作为参数传递:非Binder对象
的传递是通过在接收端复制对象完成的,但是Binder实体对象
是无法复制的,因此需要在客户进程中创建一个Binder引用对象
来代替实体对象。 -
Binder引用
对象作为参数传递:- 如果传递的目的地是该引用对象对应实体对象所在的进程,那么:
-
Binder框架
必须把这个引用对象
转换成Binder实体对象
。 - 不能再创建一个新的
实体对象
- 必须找到并使用原来的
实体对象
-
- 如果传递的目的地是另一个
客户端
进程,那么:- 不能简单的复制
引用对象
- 需要建立目的进程中的
引用对象
和实体对象
的关系 - 这个关系的建立是在
Binder驱动
中完成的
- 不能简单的复制
- 如果传递的目的地是该引用对象对应实体对象所在的进程,那么:
Binder对象传递流程简介
Binder调用
的参数传递是通过Parcel类
来完成的。先来简单看下Binder实体对象
转换成Binder引用对象
的过程:
- 在
服务进程
中将IBinder(BBinder)对象
加入到Parcel对象
后,Parcel对象
会:- 打包数据,并把数据类型标记为
BINDER_TYPE_BINDER
- 把
BpBinder
的指针放进cookie
字段 - 通过
ioctl
把Parcel对象
的数据传递到Binder驱动
中
- 打包数据,并把数据类型标记为
-
Binder驱动
会检查传递进来的数据,如果发现了标记为BINDER_TYPE_BINDER
的数据:- 会先查找和服务进程相关的
Binder实体对象表
:- 如果表中还没有这个实体对象的记录,则创建新的节点,并保存信息。
- 然后驱动会查看客户进程的
Binder对象引用表
:- 如果没有引用对象的记录,同样会创建新的节点
- 并让这个节点中某个字段指向服务进程的
Binder实体对象表
中的节点
- 接下来驱动对
Parcel对象
中的数据进行改动:- 把数据从
BINDER_TYPE_BINDER
改为BINDER_TYPE_HANEL
- 同时把
handle
的值设为Binder对象引用表
中的节点
- 把数据从
- 最后,把改动的数据传到
客户进程
- 会先查找和服务进程相关的
-
客户端
接收到数据,发现数据中的Binder类型
为BINDER_TYPE_HANEL
后- 使用
handle
值作为参数,调用ProcessState
类中的函数getStrongProxyFoHandle
来得到BpBinder对象
- 如果对象不存在则创建一个新的对象
- 这个
BpBinder对象
会一直保存在ProcessState
的mHandleToObject
表中
- 这样,客户端就得到了
Binder引用对象
- 使用
写入Binder对象的过程
有了上面的整体流程,我们来看下Binder对象
的写入细节:
- Parcel类中:
- 写入
Binder对象
的函数是:-
writeStrongBinder
:写入强引用Binder对象
-
writeWeakBinder
:写入弱引用Binder对象
-
- 读取
Binder对象
的函数是:-
readStrongBinder
:获取强引用Binder对象
- 强引用的Binder对象可以分为
实体对象
和引用对象
- 强引用的Binder对象可以分为
-
readWeakBinder
:获取弱引用Binder对象
- 弱引用则没有区分
实体对象
和引用对象
- 弱引用则没有区分
-
- 写入
看下writeStrongBinder
的代码:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
status_t flatten_binder(const sp<ProcessState>& /*proc*/,
const sp<IBinder>& binder, Parcel* out)
{
// flatten_binder 整个方法其实是在向obj这个结构体存放数据
flat_binder_object obj;
if (IPCThreadState::self()->backgroundSchedulingDisabled()) {
/* minimum priority for all nodes is nice 0 */
obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
} else {
/* minimum priority for all nodes is MAX_NICE(19) */
obj.flags = 0x13 | FLAT_BINDER_FLAG_ACCEPTS_FDS;
}
if (binder != NULL) {
// 调用localBinder函数开区分是实体对象还是引用对象
IBinder *local = binder->localBinder();
if (!local) { //binder引用对象
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
ALOGE("null proxy");
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.hdr.type = BINDER_TYPE_HANDLE;
obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
obj.handle = handle;
obj.cookie = 0;
} else { // binder实体对象
obj.hdr.type = BINDER_TYPE_BINDER;
obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
obj.cookie = reinterpret_cast<uintptr_t>(local);
}
} else {
obj.hdr.type = BINDER_TYPE_BINDER;
obj.binder = 0;
obj.cookie = 0;
}
return finish_flatten_binder(binder, obj, out);
}
flatten_binder
整个方法其实是在向flat_binder_object
这个结构体存放数据。我们看下flat_binder_object
的结构:
struct flat_binder_object {
/* 8 bytes for large_flat_header. */
__u32 type;
__u32 flags;
/* 8 bytes of data. */
union {
binder_uintptr_t binder; /* local object */
__u32 handle; /* remote object */
};
/* extra data associated with local object */
binder_uintptr_t cookie;
};
我们看下flat_binder_object
中的属性:
-
type
的类型:-
BINDER_TYPE_BINDER
:用来表示Binder实体对象 -
BINDER_TYPE_WEAK_BINDER
:用来表示Bindr实体对象的弱引用 -
BINDER_TYPE_HANDLE
:用来表示Binder引用对象 -
BINDER_TYPE_WEAK_HANDLE
:用来表示Binder引用对象的弱引用 -
BINDER_TYPE_FD
:用来表示一个文件描述符
-
-
flag
字段用来保存向驱动传递的标志 -
union.binder
在打包实体对象
时存放的是对象的弱引用指针
-
union.handle
在打包引用对象
时存放的是对象中的handle值
-
cookie
字段只用在打包实体对象
时,存放的是BBinder指针
解析强引用
Binder对象数据的过程
Parcel 类中解析数据的函数是unflatten_binder
,代码如下:
status_t unflatten_binder(const sp<ProcessState>& proc,
const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);
if (flat) {
switch (flat->hdr.type) {
case BINDER_TYPE_BINDER:
*out = reinterpret_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}
unflatten_binder
的逻辑是:
- 如果是
BINDER_TYPE_BINDER
类型的数据,说明接收到的数据类型是Binder实体对象
,此时cookie字段
存放的是本进程的Binder实体对象
的指针,可直接转化成IBinder
的指针 - 如果是
BINDER_TYPE_HANDLE
类型的数据,则调用ProcessState类
的getStrongProxyForHandle
函数来得到BpBinder
对象,函数代码如下:sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle) { sp<IBinder> result; AutoMutex _l(mLock); // 根据handle查看进程中是否已经创建了引用对象 // 如果进程中不存在handle对应的引用对象,在表中插入新的元素并返回 handle_entry* e = lookupHandleLocked(handle); if (e != NULL) { IBinder* b = e->binder; //根据返回元素中的binder值来判断是否有引用对象 if (b == NULL || !e->refs->attemptIncWeak(this)) { if (handle == 0) { // handle为0 表示是ServiceManager的引用对象 Parcel data; //发送PING_TRANSACTION检查ServiceManager是否存在 status_t status = IPCThreadState::self()->transact( 0, IBinder::PING_TRANSACTION, data, NULL, 0); if (status == DEAD_OBJECT) return NULL; } b = BpBinder::create(handle); // 创建一个新的引用对象 e->binder = b;// 放入元素中的binder属性 if (b) e->refs = b->getWeakRefs(); result = b; } else { result.force_set(b);// 如果引用对象已经存在,放入到返回的对象result中 e->refs->decWeak(this); } } return result; }
getStrongProxyForHandle()
函数会调用lookupHandleLocked()
来查找handle
在进程中对应的引用对象
。所有进程的引用对象
都保存在ProcessState
的mHandleToObject
变量中。mHandleToObject
变量定义如下:
Vector<handle_entry> mHandleToObject;
-
mHandleToObject
是一个Vector
集合类,元素类型handle_entry
-
handle_entry
结构很简单:
struct handle_entry { IBinder* binder; RefBase::weakref_type* refs; };
-
-
lookupHandleLocked()
函数就是使用handle
作为关键项
来查找对应的handle_entry
,没有则创建新的handle_entry
,并添加到集合中 - 当获得
handle_entry
后,如果handle
值为0,表明要创建的是ServiceManager
的引用对象
- 并发送
PING_TRANSACTION
消息来检查ServiceManager
是否已经创建
- 并发送
IPCThreadState类
每个Binder线程
都会有一个关联的IPCThreadState类
的对象。IPCThreadState类
主要的作用是和Binder驱动
交互,发送接收Binder数据
,处理和Binder驱动
之间来往的消息。
我们在Binder线程模型
中已经知道:
-
Binder服务
启动时,服务线程调用了joinThreadPool()
函数 - 远程调用
Binder服务
时,客户线程调用了waitForResponse()
函数
这两个函数都是定义在IPCThreadState类
中,我们分别来看下这两个函数。
waitForResponse()
函数
函数定义如下:
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
while (1) {
if ((err=talkWithDriver()) < NO_ERROR) break;//和驱动通信
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;//没有数据,重新开始循环
cmd = (uint32_t)mIn.readInt32();//读取数据
//......
switch (cmd) {
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
//......省略部分case语句
case BR_REPLY:
// Binder调用返回的消息
default:
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
finish:
//...... 错误处理
return err;
}
waitForResponse()
函数中是一个无限while循环
,在循环中,重复下面的工作:
- 调用
talkWithDriver
函数发送/接收
数据 - 如果有消息从驱动返回,会通过
switch语句
处理消息。- 如果收到错误消息或者调用返回的消息,将通过
goto
语句跳出while循环
- 如果还有未处理的消息,则交给
executeCommand
函数处理
- 如果收到错误消息或者调用返回的消息,将通过
我们再仔细看下Binder调用
收到的返回类型为BR_REPLY
的代码:
case BR_REPLY:
{
binder_transaction_data tr;
//按照 binder_transaction_data 结构大小读取数据
err = mIn.read(&tr, sizeof(tr));
ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
if (err != NO_ERROR) goto finish;
if (reply) {
//reply 不为null,表示调用者需要返回结果
if ((tr.flags & TF_STATUS_CODE) == 0) {
//binder 调用成功,把从驱动来的数据设置到reply对象中
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t),
freeBuffer, this);
} else {
//binder调用失败,使用freeBuffer函数释放驱动中分配的缓冲区
err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
}
} else {
//调用者不需要返回结果,使用freeBuffer函数释放驱动中分配的缓冲区
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
continue;
}
}
针对BR_REPLY
类型的处理流程是:
- 如果
Binder调用
成功返回,并且调用者
也需要返回值- 把接收到的数据放在
Parcel
对象reply
中返回
- 把接收到的数据放在
- 如果
Binder调用
不成功,或者调用者
不需要返回数据- 通过
freeBuffer
释放驱动中分配的缓冲区
- 通过
因为Binder
提供了一块驱动和应用层共享的内存空间
,所以在接收Binder数据
时不需要额外创建缓冲区
并再进行一次拷贝
了,但是如果不及时通知驱动释放缓冲区中占用的无用内存,会很快会耗光这部分共享空间。
上面代码中的reply->ipcSetDataReference
方法,在设置Parcel对象
的同时,同样也把freeBuffer
的指针作为参数传入到对象中,这样reply对象
删除时,也会调用freeBuffer
函数来释放驱动中的缓冲区。
waitForResponse()
函数的作用是发送Binder调用的数据并等待返回值
。为什么还需要使用循环的方式反复和驱动交互?原因有两点:
- 一是消息协议中要求应用层通过
BC_TRANSACTION
发送Binder调用数据
后:- 驱动要先给应用层回复
BC_TRANSACTION_COMPLETE
消息,表示已经说到并且认可本次Binder调用数据
。 - 然后上层应用再次调用
talkWithDriver
来等待驱动返回调用结果 - 如果调用结果返回了,会收到
BR_REPLY
消息
- 驱动要先给应用层回复
- 二是等待调用返回期间,驱动可能会给线程发送消息,利用这个线程帮忙干点活。。。。
joinThreadPool
函数
在
Binder线程池
部分已经知道:应用启动时会伴随着启动Binder服务
,而最后执行到的方法就是joinThreadPool
函数。
我们看下函数定义:
void IPCThreadState::joinThreadPool(bool isMain)
{
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
status_t result;
do {
processPendingDerefs();
//now get the next command to be processed, waiting if necessary
result = getAndExecuteCommand();//读取并处理驱动发送的消息
if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
abort();
}
// Let this thread exit the thread pool if it is no longer
// needed and it is not the main process thread.
if(result == TIMED_OUT && !isMain) {
break;
}
} while (result != -ECONNREFUSED && result != -EBADF);
mOut.writeInt32(BC_EXIT_LOOPER); //退出前向驱动发送线程退出消息
talkWithDriver(false);
}
joinThreadPool
函数的结构是一个while循环。
- 传入的参数
isMain
- 如果为
true
(通常是线程池第一个线程发起的调用),则向驱动发送BC_ENTER_LOOPER
消息- 发送
BC_ENTER_LOOPER
的线程会被驱动标记为“主”线程 - 不会在空闲时间被驱动要求退出
- 发送
- 否则,发送
BC_REGISTER_LOOPER
。 - 这两条消息都是告诉驱动:本线程已经做好准备接收驱动来的Binder调用了
- 如果为
- 进入循环,调用了
processPendingDerefs()
函数- 用来处理
IPCThreadState
对象中mPendingWeakDerefs
和mPendingStrongDerefs
的Binder对象
的引用计数-
mPendingWeakDerefs
和mPendingStrongDerefs
都是Vector
集合
-
- 当接收到驱动发来的
BR_RELEASE
消息时,就会把其中的Binder对象
放到mPendingStrongDerefs
中 - 并在
processPendingDerefs()
函数中介绍对象的引用计数
- 用来处理
- 调用
getAndExecuteCommand
函数- 函数中调用
talkWithDriver
读取驱动传递的数据 - 然后调用
executeCommand
来执行
- 函数中调用
到这里,我们再来看下talkWithDriver
和executeCommand
两个函数
talkWithDriver
函数
talkWithDriver
函数的作用是把IPCThreadState
类中的mOut变量
保存的数据通过ioctl
函数发送到驱动,同时把驱动返回的数据放到类的mIn变量
中。
talkWithDriver
函数的代码如下:
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
if (mProcess->mDriverFD <= 0) {
return -EBADF;
}
//ioctl 传输时所使用的的数据结构
binder_write_read bwr;
// Is the read buffer empty?
// 判断 mIn 中的数据是否已经读取完毕,没有的话还需要继续读取
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
// 英文描述的很详细了哟
// 如果不需要读取数据(doReceive=false,needRead=true),那么就可以准备写数据了
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;//表示要写入的长度
bwr.write_buffer = (uintptr_t)mOut.data();//要写入的数据的指针
// This is what we'll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;//这个字段后面会被填上驱动读取到的数据长度(写入到驱动的数据长度)
bwr.read_consumed = 0;// 这个字段后面会被填上从驱动返回的数据长度
status_t err;
do {
// 9.0增加了一些平台判断,可能以后要多平台去支持了吧
#if defined(__ANDROID__)
// 用ioctl和驱动交换数据
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
#else
err = INVALID_OPERATION;
#endif
if (mProcess->mDriverFD <= 0) {
// 这个情况应该是设备节点不可用
err = -EBADF;
}
} while (err == -EINTR);
if (err >= NO_ERROR) {
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < mOut.dataSize())
// 如果已经写入驱动的数据长度小于mOut中的数据长度
// 说明还没发送完,把已经写入驱动的数据移除掉
// 剩下的数据等待下次发送
mOut.remove(0, bwr.write_consumed);
else {
// 数据已经全部写入驱动,复位mOut
mOut.setDataSize(0);
// 做一些指针的清理工作
processPostWriteDerefs();
}
}
if (bwr.read_consumed > 0) {
// 说明从驱动中读到了数据,设置好mInt对象
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}
return err;
}
- 准备发送到驱动中的数据保存在成员变量
mOut
中 - 从驱动中读取到的数据保存在成员变量
mInt
中 - 调用
talkWithDriver
时,如果mInt
还有数据- 表示还没有处理完驱动发来的消息
- 本次函数调用将不会从驱动中读取数据
-
ioctl
函数- 使用的命令是
BINDER_WRITE_READ
- 需要
binder_write_read
结构体作为参数 - 驱动篇再看
- 使用的命令是
executeCommand
函数
executeCommand
函数是一个大的switch语句,处理从驱动传递过来的消息。
我们前面遇到了一些消息,大概包括:
-
BR_SPAWN_LOOP
:驱动通知启动新线程的消息 -
BR_DEAD_BINDER
:驱动通知Binder服务
死亡的消息 -
BR_FINISHED
:驱动通知线程退出的消息 -
BR_ERROR
,BR_OK
,BR_NOOP
:驱动简单的回复消息 -
BR_RELEASE
,BR_INCREFS
,BR_DECREFS
:驱动通知增加和减少Binder对象
跨进程的引用计数 -
BR_TRANSACTION
,:驱动通知进行Binder调用
的消息
重点是BR_TRANSACTION
,代码定义如下:
case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
if (result != NO_ERROR) break; // 数据异常直接退出
Parcel buffer;
//用从驱动接收的数据设置Parcel对象Buffer
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);
const pid_t origPid = mCallingPid;
const uid_t origUid = mCallingUid;
const int32_t origStrictModePolicy = mStrictModePolicy;
const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;
// 从消息中取出调用者的进程ID和euid
mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
mLastTransactionBinderFlags = tr.flags;
Parcel reply;
status_t error;
if (tr.target.ptr) {
// We only have a weak reference on the target object, so we must first try to
// safely acquire a strong reference before doing anything else with it.
if (reinterpret_cast<RefBase::weakref_type*>(
tr.target.ptr)->attemptIncStrong(this)) {
// 如果ptr指针不为空,cookie保存的是BBinder的指针
// 调用cookie的transact函数
error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
&reply, tr.flags);
// 及时清除指针
reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
} else {
error = UNKNOWN_TRANSACTION;
}
} else {
//如果tr.target.ptr为0 表示是ServiceManager
error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
}
// 如果是同步调用,则把reply对象发送回去,否则什么也不做
if ((tr.flags & TF_ONE_WAY) == 0) {
LOG_ONEWAY("Sending reply to %d!", mCallingPid);
if (error < NO_ERROR) reply.setError(error);
sendReply(reply, 0);
} else {
LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
}
mCallingPid = origPid;
mCallingUid = origUid;
mStrictModePolicy = origStrictModePolicy;
mLastTransactionBinderFlags = origTransactionBinderFlags;
}
break;
BR_TRANSACTION
消息的处理过程是:
- 把消息解析出来后放置到
Parcel
类型的buffer
对象中 - 使用消息参数里的
BBinder指针
来调用transact
函数-
transact
函数会传入函数号
、buffer
、reply
-
transact
函数根据函数号
来调用对应的服务函数
-
服务函数
执行完后,结果保存在reply
对象中
-
- 如果是同步调用,使用
sendReply
函数返回reply
对象 - 如果是异步调用,则什么也不做,结束调用
Binder驱动
Binder 驱动是整个Binder框架的核心,这部分就会详细介绍
消息协议
、内存共享机制
、对象传递
的具体细节了
应用层和驱动的消息协议
Binder应用层
的IPCThreadState
和Binder驱动
之间通过ioctl
来传递数据,因此,定义了一些ioctl命令
:
命令 | 说明 | 数据格式 |
---|---|---|
BIDNER_WRITE_READ |
向驱动读取和写入数据,既可以单独读写,也可以同时读写。通过传入的数据中有无读写数据来控制 | struct binder_write_read |
BINDER_SET_IDLE_TIMEOUT |
有定义未使用 | int64_t |
BINDER_SET_MAX_THREADS |
设置线程池的最大线程数,达到上限后驱动将不会通知应用层启动新线程 | size_t |
BINDER_SET_IDLE_PRIORITY |
有定义未使用 | int |
BINDER_SET_CONTEXT_MGR |
将本进程设置为Binder系统 的管理进程 ,只有ServiceManager 进程才会使用这个命令 |
int |
BINDER_THREAD_EXIT |
通知驱动当前线程要退出了,以便驱动清理和该线程相关的数据 | int |
BINDER_VERSION |
获取Binder 的版本号 |
struct binder_verison |
表中的命令中。最常用的命令是BIDNER_WRITE_READ
,其余都是一些辅助性命令。BIDNER_WRITE_READ
使用的数据格式定义如下:
struct binder_write_read {
binder_size_t write_size; //计划向驱动写入的字节长度
binder_size_t write_consumed; //实际写入驱动的长度,书中称驱动实际读取的字节长度
binder_uintptr_t write_buffer;//传递给驱动数据的buffer指针,也就是真正要写入的数据
binder_size_t read_size; //计划从驱动中读取的字节长度
binder_size_t read_consumed; //实际从驱动中读取到的字节长度
binder_uintptr_t read_buffer; //接收存放读取到的数据的指针
};
存放在read_buffer
和write_buffer
中的数据也是有格式的,格式是消息ID
加上binder_transaction_data
,binder_transaction_data
定义如下:
struct binder_transaction_data {
union {
__u32 handle;
binder_uintptr_t ptr;
} target; // BpBinder对象使用handle,BBinder使用ptr
binder_uintptr_t cookie; // 对于BBinder对象,这里是BBinder的指针
__u32 code; // Binder服务的函数号码
__u32 flags;
pid_t sender_pid; // 发送方的进程ID
uid_t sender_euid; // 发送方的euid
binder_size_t data_size; // 整个数据区的大小
binder_size_t offsets_size; // IPC对象区域的大小
union {
struct {
binder_uintptr_t buffer; // 指向数据区开头的指针
binder_uintptr_t offsets;// 指向数据区中IPC对对象区的指针
} ptr;
__u8 buf[8];
} data;
};
结构binder_transaction_data
中:
-
data.ptr
指向传递给驱动的数据区
的起始地址-
传递给驱动的数据区
就是从应用层传递下来的Parcel对象
的数据区
-
- 在
Parcel对象
中如果打包了IPC对象
(也就是Binder对象
),或者文件描述符
,数据区中会有一段空间专门用来保存这部分数据。- 因此使用
data.ptr.offsets
表示数据区IPC对象
的起始位置 - 用
offsets_size
表示数据区IPC对象
的大小
- 因此使用
Binder消息
则根据发送
和接收
分为两套,从命名上也比较容易区分:
-
发送类型
的指的是消息从应用层
到驱动
的过程,通常以BC_
开头 -
接收类型
的指的是消息从驱动
到应用层
的过程,通常以BR_
开头
发送给驱动的Binder命令列表
命令 | 说明 |
---|---|
BC_TRANSACTION |
发送Binder调用 的数据 |
BC_REPLY |
返回Binder调用 的返回值 |
BC_ACQUIRE_RESULT |
回应BR_ATTEMPT_ACQUIRE 命令 |
BC_FREE_BUFFER |
释放通过mmap 分配的内存块 |
BC_INCREFS |
增加Binder对象 的弱引用计数 |
BC_ACQUIRE |
增加Binder对象 的强引用计数 |
BC_DECREFS |
减少Binder对象 的弱引用计数 |
BC_RELEASE |
减少Binder对象 的强引用计数 |
BC_INCREFS_DONE |
回应BC_INCREFS 指令 |
BC_ACQUIRE_DONE |
回应BC_ACQUIRE 指令 |
BC_ATTEMPT_ACQUIRE |
将Binder对象的弱引用升级为强引用 |
BC_REGISTER_LOOPER |
把当前线程注册为线程池的主线程
|
BC_ENTER_LOOPER |
通知驱动,线程可以进入数据的发送和接收 |
BC_EXIT_LOOPER |
通知驱动,当前线程退出数据的发送和接收 |
BC_REQUEST_DEATH_NOTYFICATION |
通知驱动接收某个Binder服务 的死亡通知 |
BC_CLEAR_DEATH_NOTYFICATION |
通知驱动不再接收某个Binder服务 的死亡通知 |
BC_DEAD_BINDER_DONE |
回应BR_DEAD_BINDER 命令 |
驱动返回的命令列表
命令 | 说明 |
---|---|
BR_ERROR |
驱动内部出错了 |
BR_OK |
命令成功 |
BR_TRANSACTION |
Binder调用命令 |
BR_REPLY |
返回Binder调用的结果 |
BR_ACQUIRE_RESULT |
未使用 |
BR_DEAD_REPLY |
向驱动发送Binder调用 时,如果对方已经死亡,则驱动回应此命令 |
BR_TRANSACTION_COMPLETE |
回应BR_TRANSACTION 命令 |
BR_INCREFS |
要求增加Binder对象 的弱引用计数 |
BR_ACQUIRE |
要求增加Binder对象 的强引用计数 |
BR_DECREFS |
要求减少Binder对象 的弱引用计数 |
BR_RELEASE |
要求减少Binder对象 的强引用计数 |
BR_ATTEMPT_ACQUIRE |
要求将Binder对象 的弱引用变成强引用 |
BR_NOOP |
命令成功 |
BR_SPAWN_LOOPER |
通知创建Binder线程
|
BR_FINISHED |
未使用 |
BR_DEAD_BINDER |
通知关注的Binder 已经死亡 |
BR_CLEAR_DEATH_NOTYFICATION_DONE |
回应BC_CLEAR_DEATH_NOTYFICATION
|
BR_FAILED_REPLY |
如果Binder调用 的函数号 不正确,回复本消息 |
Binder消息序列图
前面列出的两个命令列表看上去指令蛮多的,其实可以分为四类:
-
Binder线程
管理相关 -
Binder方法调用
相关 -
Binder对象引用计数管理
相关 -
Binder对象死亡通知
相关
看下面两个序列图:
Binder调用
的消息序列图:
Binder对象引用计数管理
的消息序列图:
Binder驱动
分析
Binder驱动
中定义的操作函数如下:
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
Binder驱动
中并没有实现常用的read
、write
操作。数据的传输都是通过ioctl
操作来完成的。
Binder驱动
中定义了很多的数据结构,一些主要的数据结构如下:
数据结构 | 说明 |
---|---|
binder_proc |
每个使用open 打开Binder设备文件 的进程都会在驱动中创建一个biner_proc的 结构,用来记录该进程的各种信息和状态。例如:Binder节点表 、节点引用表 等 |
binder_thread |
每个Binder线程 在Binder驱动 中都有一个binder_thread 结构,记录线程相关的信息,例如要完成的任务等 |
binder_node |
binder_proc 中有一张Binder节点对象表 ,表项是binder_node 结构。代表进程中的BBinder对象,记录BBinder对象的指针、引用计数等数据 |
binder_ref |
binder_proc 中还有一张节点引用表 ,表项是binder_ref 结构。代表进程中的BpBinder对象 ,保存所引用的对象binder_node 指针。BpBinder 中的mHandler 值,就是它在索引表中的位置 |
binder_buffer |
驱动通过mmap 的方式创建了一块大的缓存区,每次Binder 传输数据,会在缓存区分配一个binder_buffer 的结构来保存数据 |
在Binder驱动中,大量使用红黑树
来管理数据。Binder驱动
中是由内核提供的实现,具体表现是生命的一个全局变量binder_procs
:
static HLIST_HEAD(binder_procs);
所有进程的binder_proc
结构体都将插入到这个变量代表的红黑树
中。以binder_open
的代码为例:
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
struct binder_device *binder_dev;
binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
current->group_leader->pid, current->pid);
proc = kzalloc(sizeof(*proc), GFP_KERNEL);//分配空间给binder_proc结构体
//......
get_task_struct(current->group_leader);// 获取当前进程的task结构
proc->tsk = current->group_leader;
//......
INIT_LIST_HEAD(&proc->todo);// 初始化binder_porc中的todo队列
//......
filp->private_data = proc;// 将proc保存到文件结构中,供下次调用使用
mutex_lock(&binder_procs_lock);
// 将proc插入到全局变量binder_procs中
hlist_add_head(&proc->proc_node, &binder_procs);
mutex_unlock(&binder_procs_lock);
//......
return 0;
}
binder_open
函数主要功能是:
- 打开
Binder驱动
的设备文件 - 为当前进程创建和初始化
binder_proc
结构体proc
- 将
proc
插入到红黑树binder_procs
中 - 将
proc
放入到file
结构的private_data
字段中- 调用驱动的其他操作时可以从
file
结构中取出代表当前进程的binder_proc
- 调用驱动的其他操作时可以从
Binder的内存共享机制
前面介绍了,用户进程打开Binder设备
后,会调用mmap
在驱动中创建一块内存空间用于接收传递给本进程的Binder数据
。我们看下mmap
的代码(这部分是9.0的源码,和书中的结构有些不同):
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
struct binder_proc *proc = filp->private_data;
const char *failure_string;
if (proc->tsk != current->group_leader)
return -EINVAL;
// 检查要求分配的空间是否大于SZ_4M的定义
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
// 检查mmap的标记,不能带有FORBIDDEN_MMAP_FLAGS
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
ret = -EPERM;
failure_string = "bad vm_flags";
goto err_bad_arg;
}
//fork 的子进程无法复制映射空间,并且不允许修改属性
vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
vma->vm_flags &= ~VM_MAYWRITE;
vma->vm_ops = &binder_vm_ops;
vma->vm_private_data = proc;
// 真正内存分配的逻辑在 binder_alloc_mmap_handler中
ret = binder_alloc_mmap_handler(&proc->alloc, vma);
//......
}
int binder_alloc_mmap_handler(struct binder_alloc *alloc,
struct vm_area_struct *vma)
{
int ret;
struct vm_struct *area;
const char *failure_string;
struct binder_buffer *buffer;
mutex_lock(&binder_alloc_mmap_lock);
//如果已分配缓存区,goto 错误
if (alloc->buffer) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
//在用户进程分配一块内存作为缓冲区
area = get_vm_area(vma->vm_end - vma->vm_start, VM_ALLOC);
//如果内存分配失败,goto 对应错误
if (area == NULL) {
ret = -ENOMEM;
failure_string = "get_vm_area";
goto err_get_vm_area_failed;
}
//把分配的缓冲区指针放在binder_proc的buffer字段
alloc->buffer = area->addr;
//重新配置下内存起始地址和偏移量
alloc->user_buffer_offset =
vma->vm_start - (uintptr_t)alloc->buffer;
mutex_unlock(&binder_alloc_mmap_lock);
//创建物理页结构体
alloc->pages = kzalloc(sizeof(alloc->pages[0]) *
((vma->vm_end - vma->vm_start) / PAGE_SIZE),
GFP_KERNEL);
alloc->buffer_size = vma->vm_end - vma->vm_start;
//在内核分配struct buffer的内存空间
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
//这部分和书中着实不太一样,看的晕晕的
//目前只看出内存地址关联部分
buffer->data = alloc->buffer;
list_add(&buffer->entry, &alloc->buffers);
buffer->free = 1;
binder_insert_free_buffer(alloc, buffer);
// 异步传输将可用空间大小设置为映射大小的一半
alloc->free_async_space = alloc->buffer_size / 2;
barrier();
alloc->vma = vma;
alloc->vma_vm_mm = vma->vm_mm;
/* Same as mmgrab() in later kernel versions */
atomic_inc(&alloc->vma_vm_mm->mm_count);
return 0;
// ......对应的错误信息
}
内存分配时:
- 首先调用
get_vm_area
在用户进程
分配一块地址空间 - 接着在
内核
中也分配同样页数大小的空间 - 然后把它们的
物理内存
地址绑定在一起 - 这样
应用
和内核
间就能共享一块空间了
当发生Binder调用时:
- 数据会从
调用进程
复制到内核空间
中 - 驱动会在
服务进程
的缓冲区
中寻找一块合适大小的空间来存放数据- 因为
服务进程
的用户空间
的缓冲区
和内核空间
的缓冲区
是共享的 - 所以
服务进程
不需要将数据再从内核空间
复制到用户空间
- 节省了一次复制过程
- 提高了
Binder通信
效率
- 因为
驱动的ioctl
操作
ioctl
在驱动中的实现是binder_ioctl
函数,用来处理ioctl
操作的命令,这些命令最常见的就是BINDER_WRITE_READ
,用于Binder调用
,先看下这个命令的处理过程:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
// 检查binder_stop_on_user_error的值是否小于2,这个值表示Binder中的错误数
// 大于2则挂起当前进程到binder_user_error_wait的等待队列上
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
goto err_unlocked;
// 获取当前线程的数据结构
thread = binder_get_thread(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
switch (cmd) {
case BINDER_WRITE_READ:
//调用 binder_ioctl_write_read 方法
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
break;
}
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread *thread)
{
int ret = 0;
struct binder_proc *proc = filp->private_data;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
if (size != sizeof(struct binder_write_read)) {
ret = -EINVAL;
goto out;
}
// 这里应该就是就是一次拷贝,把数据复制到内核中
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
if (bwr.write_size > 0) {
// 如果命令时传递给驱动的数据,调用 binder_thread_write 处理
ret = binder_thread_write(proc, thread,
bwr.write_buffer,
bwr.write_size,
&bwr.write_consumed);
//省略异常处理和log打印
}
if (bwr.read_size > 0) {
// 如果命令时读取驱动的数据,调用 binder_thread_read 处理
ret = binder_thread_read(proc, thread, bwr.read_buffer,
bwr.read_size,
&bwr.read_consumed,
filp->f_flags & O_NONBLOCK);
trace_binder_read_done(ret);
binder_inner_proc_lock(proc);
// 如果进程的todo队列不为空,唤醒用户进程处理
if (!binder_worklist_empty_ilocked(&proc->todo))
binder_wakeup_proc_ilocked(proc);
binder_inner_proc_unlock(proc);
//省略异常处理和log打印
}
// 异常处理
}
BINDER_WRITE_READ
命令有可能是向驱动写数据,也可能是从驱动读数据
- 写数据,通过
binder_thread_write
方法 - 读数据,通过
binder_thread_read
方法 - 在下一节
调用过程
会细讲这两个方法
除了BINDER_WRITE_READ
命令外,还有像BINDER_SET_MAX_THREADS
、BINDER_SET_CONTEXT_MGR
等辅助通信的命令,大家可以阅读下源码。
学习的重点还是看调用过程
的数据读写部分
Binder调用
过程
Binder调用
是Binder驱动
中最核心的活动,整个过程几乎涉及了Binder驱动
所有的数据结构。我们先从调用的整体流程上来看下:
-
Binder调用
从客户进程
中发起- 通过
ioctl
操作来运行驱动
的binder_ioctl
- 通过
-
驱动
接收到命令BC_TRANSACTION
后- 找到代表
服务进程
的binder_proc
结构体 - 在
服务进程
的缓冲区分配一块内存来保存从客户进程
传递的数据 -
ioctl
回复消息BR_TRANSACTION_COMPLETED
- 找到代表
- ---------到这里,本次
ioctl
调用结束--------- -
客户进程
收到回复消息后- 再次调用
ioctl
进入等待,准备读取数据,直到调用结果返回
- 再次调用
服务进程
无法知道何时会有Binder调用
到达,因此它至少由一个线程在ioctl
上等待。
-
驱动
会在有调用到达时唤醒等待的线程,并将数据传给服务进程
- 同一时刻可能有多个
Binder调用
到达-
驱动
为每个调用创建一个binder_transaction
的结构体 - 将
结构体
中的work字段
插入到服务进程
的todo队列
中-
todo队列
是一个binder_work
结构的列表 - 每个进程都会有一个
todo队列
来接收需要完成的工作 - 当
服务端
的用户进程
对ioctl
执行读操作时- 会循环执行
todo队列
中需要完成的任务 - 直到队列为空才挂起等待
- 会循环执行
-
-
客户进程的调用流程
上面刚刚讲到,客户进程
发送的Binder调用
消息会执行到函数binder_thread_write
,这个函数的功能就是处理所有用户进程
发送的消息,我们看下和Binder调用
有关的部分:
static int binder_thread_write(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed)
{
//......
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
// 包结构体tr的数据复制到内核
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
// 调用 binder_transaction 函数处理
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
break;
}
//......
}
对BC_TRANSACTION
命令的处理就是调用了binder_transaction
函数,binder_transaction
函数既要处理BC_TRANSACTION
也会处理BC_REPLY
消息,我们先看读取的情况,也就是reply=false
的情况:
- 找到代表目标进程的节点:
if (tr->target.handle) {
struct binder_ref *ref;
//查找handle对应的引用项
ref = binder_get_ref_olocked(proc, tr->target.handle,
true);
if (ref) {
//引用项的node字段保存了Binder对象节点的指针
//复制给target_node
target_node = binder_get_node_refs_for_txn(
ref->node, &target_proc,
&return_error);
} else {
//......
}
//......
} else {
//把管理进程的节点名放到target_node中
target_node = context->binder_context_mgr_node;
//......
}
- 搜寻目标线程
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
struct binder_transaction *tmp;
tmp = thread->transaction_stack;
//......
while (tmp) {
struct binder_thread *from;
spin_lock(&tmp->lock);
from = tmp->from;
if (from && from->proc == target_proc) {
atomic_inc(&from->tmp_ref);
target_thread = from;
spin_unlock(&tmp->lock);
break;
}
spin_unlock(&tmp->lock);
tmp = tmp->from_parent;
}
}
上面这段代码的逻辑是:
- 如果本次调用不是异步调用,并且调用者线程中的
transaction_stack
不为NULL
,则在其中查找和本次调用具有相同目标进程的transaction_stack
,如果找到,则把它的目标线程设置为本次调用的目标线程。 -
transaction_stack
是一个列表,保存了本线程所有正在执行的Binder调用的binder_transaction
结构体。 -
Binder驱动
中用binder_transaction
来保存一次Binder调用
的所有数据,包括传递的数据、通信双发的进程、线程信息等。因为Binder调用涉及两个进程,还要向调用端传递返回值。 - 所以,驱动中用结构
binder_transaction
保存还没结束的Binder调用
。 - 通常情况下执行
Binder调用
时不会存在相同进程的Binder调用
,因此target_thread
的值大多为NULL
- 如果有目标线程,则使用
目标线程
中的todo队列
,否则使用目标进程
的todo队列
if (target_thread)
e->to_thread = target_thread->pid;
e->to_proc = target_proc->pid;
- 为当前的
Binder调用
创建binder_transaction
结构,并用调用的数据填充它。
t = kzalloc(sizeof(*t), GFP_KERNEL);
//......
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
//......
if (!reply && !(tr->flags & TF_ONE_WAY))
t->from = thread;
else
t->from = NULL;
t->sender_euid = task_euid(proc->tsk);
t->to_proc = target_proc;
t->to_thread = target_thread;
t->code = tr->code;
t->flags = tr->flags;
if (!(t->flags & TF_ONE_WAY) &&
binder_supported_policy(current->policy)) {
/* Inherit supported policies for synchronous transactions */
t->priority.sched_policy = current->policy;
t->priority.prio = current->normal_prio;
} else {
/* Otherwise, fall back to the default priority */
t->priority = target_proc->default_priority;
}
- 在
目标进程
(也就是服务端
)的缓冲区分配空间,复制用户进程
的数据到内核
t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
tr->offsets_size, extra_buffers_size,
!reply && (t->flags & TF_ONE_WAY));
//......
if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
tr->data.ptr.buffer, tr->data_size)) {
//......
}
if (copy_from_user(offp, (const void __user *)(uintptr_t)
tr->data.ptr.offsets, tr->offsets_size)) {
//......
}
- 这里复制的数据是
Binder调用
的参数数据,也是需要传递给服务进程
的数据,因此需要缓冲区。这个缓冲区是在目标进程
的大的缓冲区中分配的
- 处理传输中的
Binder对象
off_end = (void *)off_start + tr->offsets_size;
sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));
sg_buf_end = sg_bufp + extra_buffers_size;
off_min = 0;
for (; offp < off_end; offp++) {
//......
}
- 通过参数传递的
Binder对象
需要进行转换
,这里的for循环
就是进行转换操作,内容较多,会在下面处理传递的Binder对象
章节进行讲解
- 将本次调用的
binder_transaction
结构体链接到线程的binder_stack
列表中
if (!(t->flags & TF_ONE_WAY)) {
//......
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
// 书中下面注释部分的代码已经整合到 binder_proc_transaction 函数中了
// 首先,将结构 binder_transaction中的binder_work放到目标目标进程或线程的todo队列
// 然后,创建一个新的binder_work的结构体,并将它放到发送线程的todo队列
if (!binder_proc_transaction(t, target_proc, target_thread)) {
//...... 失败处理
}
}
-
binder_transaction
结构中包含了一个binder_work
的结构体,因此它可以被放到todo队列
中 - 本地调用时
binder_work
的type
被设置为BINDER_WORK_TRANSACTION
后,插入了目标进程
或目标线程
的todo队列
中 - 为了使
客户进程
能收到回复消息,这里也会创建一个新的binder_work
的结构体,并把它的type
设置成了BINDER_WORK_TRANSAXTION_COMPLETET
,并将它插入到当前线程的todo队列
中
前面介绍过,在binder_thread_write
函数执行完后,还会去判断是否需要执行binder_thread_read
函数:
- 由于调用端执行完
BC_TRANSACTION
后,会立刻执行ioctl
的读
指令 - 所以,在调用操作上,
binder_thread_read
函数算是紧接着binder_thread_write
函数后执行的
刚才新建的binder_work
的结构体已经插入todo队列
了,我们看下binder_thread_read
函数会进行哪些和todo队列
相关的操作:
while (1) {
//......
// 获得todo队列,获取失败则goto retry
if (!binder_worklist_empty_ilocked(&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(&proc->todo) &&
wait_for_proc_work)
list = &proc->todo;
else {
binder_inner_proc_unlock(proc);
/* no data added */
if (ptr - buffer == 4 && !thread->looper_need_return)
goto retry;
break;
}
//取出todo队列中的元素
w = binder_dequeue_work_head_ilocked(list);
//......
switch (w->type) {
//......省略大量case语句
case BINDER_WORK_TRANSACTION_COMPLETE: {
binder_inner_proc_unlock(proc);
cmd = BR_TRANSACTION_COMPLETE;
//把返回消息通过put_user放到用户空间的指针中
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
binder_stat_br(proc, thread, cmd);
kfree(w);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
} break;
//......省略大量case语句
}
//......
}
-
binder_thread_read
函数所做的处理就是把回复消息BR_TRANSACTION_COMPLETE
复制到用户空间 - 这样
客户进程
就可以收到回复消息了
到这里,客户进程
的调用结束了。但是,Binder调用
才完成一半,接下来,看看服务进程
是如何调用数据的。
服务进程
的调用流程
服务进程
至少有一个线程会在ioctl上等待调用的到来。服务进程
调用ioctl时传递的是读数据的请求,所以最后调用的也是binder_thread_read
函数,我们看下binder_thread_read
函数完整的处理流程:
- 如果保存返回结果的缓冲区中还没有数据,先写入
BR_NOOP
消息:
if (*consumed == 0) {
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}
- 进入循环处理所有
todo队列
中的工作
while (1) {
//......
}
- 读取线程或进程
todo队列
中需要完成的工作
struct binder_work *w = NULL;
//......
if (!binder_worklist_empty_ilocked(&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(&proc->todo) &&
wait_for_proc_work)
list = &proc->todo;
//......
w = binder_dequeue_work_head_ilocked(list);
- 用
switch语句
处理所有类型的工作
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
binder_inner_proc_unlock(proc);
t = container_of(w, struct binder_transaction, work);
} break;
}
- 经过
客户进程的调用流程
后,此时的服务进程
中已经存在一个类型为BINDER_WORK_TRANSACTION
的工作需要处理 - 这里只是取出了和
binder_work
关联的binder_transaction
结构体指针 - 并保存到变量
t
中
- 调整线程的优先级
if (!t)
continue;
if (t->buffer->target_node) {
struct binder_node *target_node = t->buffer->target_node;
struct binder_priority node_prio;
tr.target.ptr = target_node->ptr;
tr.cookie = target_node->cookie;
node_prio.sched_policy = target_node->sched_policy;
node_prio.prio = target_node->min_priority;
binder_transaction_priority(current, t, node_prio,
target_node->inherit_rt);
cmd = BR_TRANSACTION;
}
- 如果t为NULL,继续循环
- 否则,开始准备返回的消息
BR_TRANSACTION
- 同时,设置线程的优先级
- 如果
调用线程
的优先级低于当前线程
指定的最低优先级
,则把当前线程
的优先级设为调用线程
的优先级 - 否则,把
当前线程
设为指定的最低优先级
- 这意味着
Binder线程
会以尽量低的优先级运行
- 如果
- 准备返回的数据
tr.code = t->code;
tr.flags = t->flags;
tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);
//......
// 让tr中的data指针指向内核中保存的数据缓冲区
tr.data_size = t->buffer->data_size;
tr.offsets_size = t->buffer->offsets_size;
tr.data.ptr.buffer = (binder_uintptr_t)
((uintptr_t)t->buffer->data +
binder_alloc_get_user_buffer_offset(&proc->alloc));
tr.data.ptr.offsets = tr.data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));
// 把 BR_TRANSACTION 消息复制到用户空间
if (put_user(cmd, (uint32_t __user *)ptr)) {
//......
return -EFAULT;
}
ptr += sizeof(uint32_t);
if (copy_to_user(ptr, &tr, sizeof(tr))) {
//把结构体tr数据复制到用户空间
//......
return -EFAULT;
}
ptr += sizeof(tr);
//......
break;//跳出while循环
这一段代码都是为消息BR_TRANSACTION
准备返回数据,要注意的是:
- 调用
copy_to_user
复制到用户空间
的只是结构体tr
的数据 -
服务进程
得到这个结构体之后,会直接读取它里面的data指针
的数据 - 数据准备完毕后,使用
break语句
跳出while循环
- 启动新线程
if (proc->requested_threads == 0 &&
list_empty(&thread->proc->waiting_threads) &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
/*spawn a new thread if we leave this out */) {
proc->requested_threads++;
//......
if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
return -EFAULT;
}
当前线程要执行Binder调用
,新来的调用也需要线程来处理,因此:
- 函数结束前会检查进程中可用的线程数
- 如果需要创建新线程,则在返回的
buffer数据
中增加BR_SPAWN_LOOPER
消息,服务进程
收到这个消息会启动新的线程
- 如果需要创建新线程,则在返回的
完整的Binder调用过程还需要把回复消息传递给客户进程,这个过程使用的函数还是前面的这些,暂时不分析了。消化一下先
处理传递的Binder对象
前面介绍了Binder对象
传递的原理和用户层的实现。(原理上整个人还是晕晕的),我们来看下Binder驱动
如何实现Binder对象
的传递的。
-
Binder驱动
中,代表每个进程的结构binde_proc
中有两个字段:nodes
和refs_by_node
- 这两个字段各指向两颗红黑树的头
-
nodes
:指向的是Binder节点对象表
,储存本进程中Binder实体对象
相关的数据 -
refs_by_node
:指向的是Binder引用对象表
,存储本进程的Binder引用对象
的数据以及对应的实体对象的节点指针
来个抽象点的图:
Binder驱动
中处理对象转换的代码位于函数binder_transaction
中
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_binder(fp, t, thread);
} break;
static int binder_translate_binder(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
struct binder_node *node;
struct binder_proc *proc = thread->proc;
struct binder_proc *target_proc = t->to_proc;
struct binder_ref_data rdata;
int ret = 0;
//根据binder值查找Binder对象表中的节点
node = binder_get_node(proc, fp->binder);
if (!node) { //没有则新建一个节点
node = binder_new_node(proc, fp);
if (!node)
return -ENOMEM;
}
//......
if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
ret = -EPERM;
goto done;
}
// 在接收端进程中寻找节点的引用,找不到会创建一个新的引用
ret = binder_inc_ref_for_node(target_proc, node,
fp->hdr.type == BINDER_TYPE_BINDER,
&thread->todo, &rdata);
if (ret)
goto done;
// 将传递的binder数据结构的type的值改为 BINDER_TYPE_HANDLE
if (fp->hdr.type == BINDER_TYPE_BINDER)
fp->hdr.type = BINDER_TYPE_HANDLE;
else
fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
fp->binder = 0;
// rdata.desc存放的是节点引用表中的序号,赋值给handle
fp->handle = rdata.desc;
fp->cookie = 0;
//......
}
上面的流程是
- 通过
binder_get_node
函数在发送进程的Binder对象节点表
中查找节点- 查找是通过比较数据中的
binder字段
和节点中对应的字段进行的 -
binder字段
中存放的是Binder对象
的弱引用指针 - 如果没找到
- 新建节点
- 把
binder字段
、cookie字段
的值保存到新节点
- 查找是通过比较数据中的
- 通过
binder_inc_ref_for_node
函数在目标进程查找Binder节点
的引用
。- 没找到会新建一个
-
引用
指的是节点引用表
中的refs_by_node
节点,包含指向Binder节点
的指针
- 把数据的
type
字段的值改为BINDER_TYPE_HANDLE
或BINDER_TYPE_WEAK_HANDLE
- 把
handle
字段的值设为在节点引用表
的序号,这也是Binder引用对象
中handle值
的来历
下面再看看如何处理类型BINDER_TYPE_HANDLE
或BINDER_TYPE_WEAK_HANDLE
的
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_handle(fp, t, thread);
} break;
static int binder_translate_handle(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
//......
// 通过handle值查找节点引用
node = binder_get_node_from_ref(proc, fp->handle,
fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata);
if (!node) {
//.....
return -EINVAL;
}
//......
if (node->proc == target_proc) {
// 如果目标进程就是Binder对象的进程,开始转换
if (fp->hdr.type == BINDER_TYPE_HANDLE)
fp->hdr.type = BINDER_TYPE_BINDER;
else
fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
fp->binder = node->ptr;
fp->cookie = node->cookie;
if (node->proc)
binder_inner_proc_lock(node->proc);
binder_inc_node_nilocked(node,
fp->hdr.type == BINDER_TYPE_BINDER,
0, NULL);
//......
} else {
//......
// 如果不是,则在目标进程新建一个Binder节点的引用
ret = binder_inc_ref_for_node(target_proc, node,
fp->hdr.type == BINDER_TYPE_HANDLE,
NULL, &dest_rdata);
//......
fp->handle = dest_rdata.desc;
fp->cookie = 0;
trace_binder_transaction_ref_to_ref(t, node, &src_rdata,
//......
}
}
这部分代码的流程是:
- 通过传输数据的
handle字段
在发送进程的节点引用表
中查找- 正常情况下是可以找到,不能就错误返回
- 如果目标进程就是Binder对象所在的进程
- 开始进行转换,把数据的
type字段
转为BINDER_TYPE_BINDER
或BINDER_TYPE_WEAK_BINDER
BINDER_WORK_TRANSACTION - 把
binder
和cookie
字段设置为节点中保存的值
- 开始进行转换,把数据的
- 如果目标进程不是Binder对象所在的进程
- 在目标对象中建立一个节点对象的引用
到这里呢,Binder原理部分就差不多了,已经了解了包括:
- 客户端
调用
、接收
消息的过程 - 服务端
监听
、回复
消息的过 - 驱动
传输数据
、调度线程
的操作
现在,我们再来看最后的一小部分:ServiceManager
的作用
ServiceManager
的作用
关于ServiceManager
,先简单描述:
-
ServiceManager
是Binder架构
中用来解析Binder名称
的模块 -
ServiceManager
本身就是一个Binder服务
-
ServiceManager
并没有使用libbinder
来构建Binder服务
- 2020-09-04 同步了下项目代码,发现也替换成
libbinder
那一套了。。。。。 - 不确定是不是更换仓储了
- 要是这样就没意思了,简易版本对于加深
binder
理解还是很有帮助的 - 我们暂且按照老的版本来看下吧
- 2020-09-04 同步了下项目代码,发现也替换成
-
ServiceManager
自己实现了一个简单的binder框架
来直接和驱动通信。。。
ServiceManager
源码路径在:frameworks/native/cmds/servicemanager
,主要包含两个文件:
-
binder.c
:用来实现简单的Binder通信功能- 简单版本的
binder协议
实现 - 直接的
ioctl
操作与binder驱动
通信 - 不是本节的重点,感兴趣可以参照源码来学习啦
- 简单版本的
-
service_manager.c
:用来实现ServiceManager
的业务逻辑- 重点是了解
ServiceManager
如何响应Binder服务
的注册和查询的
- 重点是了解
-
这么重要的服务要在什么时间启动呢?
- 这部分是在
system/core/rootdir/init.rc
中配置
on post-fs ...... # start essential services start logd start servicemanager start hwservicemanager start vndservicemanager
-
hwservicemanager
用来支持HIDL
-
vndservicemanager
第三方厂商使用,应该是从Treble架构
中出现的
- 这部分是在
ServiceManager
的架构
从ServiceManager
的main
函数开始:
int main(int argc, char** argv)
{
struct binder_state *bs;
union selinux_callback cb;
char *driver;
if (argc > 1) {
driver = argv[1];
} else {
driver = "/dev/binder";
}
bs = binder_open(driver, 128*1024);
if (!bs) {
//......
// 省略一些宏判断
return -1;
}
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
// 设置selinux callback
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
cb.func_log = selinux_log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
//......
// 省略一些宏判断,都是为了获取sehandle(检查SELinux权限)
sehandle = selinux_android_service_context_handle();
selinux_status_open(true);
if (sehandle == NULL) {
ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");
abort();
}
if (getcon(&service_manager_context) != 0) {
ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
abort();
}
binder_loop(bs, svcmgr_handler);
return 0;
}
main
函数的流程如下:
- 首先调用
binder_open
来打开binder设备
和初始化系统- 同时创建一块
128x1024
大小的内存空间
- 同时创建一块
- 接着调用
binder_become_context_manager
把本进程设置为Binder框架
的管理进程-
binder_become_context_manager
函数代码如下:
int binder_become_context_manager(struct binder_state *bs) { return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0); }
- 函数灰常简单,直接通过
ioctl
把控制命令BINDER_SET_CONTEXT_MGR
发到了驱动
-
- 最后执行
binder_loop(bs, svcmgr_handler)
,简单处理后,循环等待消息
我们要说一说binder_loop(bs, svcmgr_handler)
,小弟C语言
不熟,感觉这个操作很SAO
:
-
binder_loop
的函数定义是:
void binder_loop(struct binder_state *bs, binder_handler func)
- 第二个参数传入的是一个
binder_handler
类型
- 再来看下
binder_handler
类型的定义:
typedef int (*binder_handler)(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply);
- 这是定义了一个
函数指针类型
- 返回值是
int
- 再来看下
binder_loop(bs, svcmgr_handler)
中的svcmgr_handler
函数的声明
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
//......
// 省略一些switch语句,稍后详解
return 0;
}
- 传入的指针是
svcmgr_handler
函数指针 - 应该会自动转型为
binder_handler
指针类型 - 毕竟函数参数、返回值都是一样的
- 这部分是我觉得最神奇的地方。。。。。
- 特意写了个C代码试了下,
- 如果定义的函数的
参数
、返回值
与typedef
定义的函数指针
不一致的话,强转编译会失败 - 不过这种操作感觉还是很不正经。。(还是Java更严谨一些)
- 参数理解的差不多了,我们仔细看下
binder_loop
的实现,这两个参数传进去干了啥。PS:简化版就是更容易理解些。。。。。。
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;
binder_write(bs, readbuf, sizeof(uint32_t));
for (;;) {
//无限循环
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
// 开始读取数据
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
// 异常通信,跳过此次,继续循环
ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
break;
}
// 读取到数据后
// 调用 binder_parse 进行数据解析
// 顺便把 binder_handler 函数指针也传递进去
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
// 异常情况,继续循环
if (res == 0 || res < 0) {
//......
break;
}
}
}
- 有注释,很很简洁的逻辑
- 最后执行到了
binder_parse
函数
- 我们继续跟进
binder_parse
函数
int binder_parse(struct binder_state *bs, struct binder_io *bio,
uintptr_t ptr, size_t size, binder_handler func)
{
int r = 1;
uintptr_t end = ptr + (uintptr_t) size;
while (ptr < end) {
//取指令
uint32_t cmd = *(uint32_t *) ptr;
switch(cmd) {
//......
// 由于binder_handler 在这里会被执行到
// 所以我们先重点看这个,当Binder调用过来时
case BR_TRANSACTION: {
struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
// 数据检查
if ((end - ptr) < sizeof(*txn)) {
ALOGE("parse: txn too small!\n");
return -1;
}
binder_dump_txn(txn);
// 如果函数指针存在
if (func) {
unsigned rdata[256/4];
struct binder_io msg;
struct binder_io reply;
int res;
// 一些初始化操作
bio_init(&reply, rdata, sizeof(rdata), 4);
bio_init_from_txn(&msg, txn);
//执行 binder_handler 函数指针指向的函数
res = func(bs, txn, &msg, &reply);
if (txn->flags & TF_ONE_WAY) {
// 这种情况不会返回结果
binder_free_buffer(bs, txn->data.ptr.buffer);
} else {
// 返回结果数据
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
}
}
ptr += sizeof(*txn);
break;
}
//......
}
return r;
}
- 还是看注释哈,代码很简洁,注释很详细,哈哈哈
- 到这里,我们可以看出来,真正处理
调用服务
的是binder_handler
这个函数指针啊 - 也就是说
远程调用
的处理逻辑在svcmgr_handler
这个函数呀- 666!
- 我们来看下
svcmgr_handler
函数
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
//......
// 如果请求的目标服务不是ServiceManager,直接返回
if (txn->target.ptr != BINDER_SERVICE_MANAGER)
return -1;
// 如果请求消息内容只是简单的测试通路,不需要继续执行,直接返回 0
if (txn->code == PING_TRANSACTION)
return 0;
// 检查收到的消息 id 串
strict_policy = bio_get_uint32(msg);
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
//......
// 检查SELinux 的权限
if (sehandle && selinux_status_updated() > 0) {
struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
if (tmp_sehandle) {
selabel_close(sehandle);
sehandle = tmp_sehandle;
}
}
switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
// 处理查询或者获取服务的指令
//......
break;
case SVC_MGR_ADD_SERVICE:
// 处理注册服务的的指令
//......
break;
case SVC_MGR_LIST_SERVICES: {
//......
// 处理获取服务列表的指令
}
default:
ALOGE("unknown code %d\n", txn->code);
return -1;
}
//发送返回消息
bio_put_uint32(reply, 0);
return 0;
}
-
ServiceManager
的架构非常简单高效,只有一个循环来和binder驱动
进行通信
ServiceManager
提供的服务
从svcmgr_handler
函数中可以看到,ServiceManager
提供了三种服务功能:
- 注册
Binder服务
- 查询
Binder服务
- 获取
Binder服务
列表
注册Binder服务
在case SVC_MGR_ADD_SERVICE
中实现的注册Binder服务
功能,具体的实现函数是do_add_service
,代码如下:
int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle,
uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid) {
struct svcinfo *si;
// 一些基础的信息判断
if (!handle || (len == 0) || (len > 127))
return -1;
// 检查调用进程是否有权限注册服务
if (!svc_can_register(s, len, spid, uid)) {
//......省略log打印
return -1;
}
// 查看要注册的服务是否已经存在
si = find_svc(s, len);
if (si) {
// 如果存在,先把以前的Binder对象的引用计数减一
if (si->handle) {
svcinfo_death(bs, si);
}
// 把原先节点中的handle替换成新的handle
si->handle = handle;
} else {
// 服务不存在,则生成新的列表项,初始化后加入列表
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
if (!si) {
// 内存申请失败,直接退出
return -1;
}
// 一些初始化操作
si->handle = handle;
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
si->name[len] = '\0';
si->death.func = (void*) svcinfo_death;
si->death.ptr = si;
si->allow_isolated = allow_isolated;
si->dumpsys_priority = dumpsys_priority;
si->next = svclist;
svclist = si;
}
// 增加Binder服务的引用计数
binder_acquire(bs, handle);
// 注册该Binder服务的死亡通知
binder_link_to_death(bs, handle, &si->death);
return 0;
}
do_add_service
函数的流程是:
- 首先检查调用的进程是否有注册服务的权限
- 这部分是通过
SELinux
来控制的,后面会学习到
- 这部分是通过
- 接着检查需要注册的服务是否已经存在
- 存在,把原来
Binder服务
在驱动的引用计数减一
- 不存在
- 新创建一个
scvinfo
结构 - 填充需要待注册服务的相关信息到结构中
- 把结构加入到服务列表
svclist
- 新创建一个
- 存在,把原来
- 通知内核把
Binder服务
的引用计数加一
- 注册该服务的死亡通知
有木有发现对于服务已经存在的情况:
- 进行了先
减一
再加一
的操作 - 是不是可以不用操作计数也可以呢?
可以思考下
查询Binder服务
在case SVC_MGR_CHECK_SERVICE
中处理查询服务的功能,具体的实现接口是do_find_service
函数,代码如下:
uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
struct svcinfo *si = find_svc(s, len);
if (!si || !si->handle) {
return 0;
}
if (!si->allow_isolated) {
// If this service doesn't allow access from isolated processes,
// then check the uid to see if it is isolated.
uid_t appid = uid % AID_USER;
if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {
return 0;
}
}
if (!svc_can_find(s, len, spid, uid)) {
return 0;
}
return si->handle;
}
do_find_service
函数的主要工作是搜索列表、返回查找到的服务。请注意有一段判断uid的代码:
- 在调用
ServiceManager
的addBinder
服务时有个参数allowed_isolated
,用来指定服务是否允许从沙箱中访问 - 这里的代码应该是判断调用进程是否是一个
隔离进程
- 如果
appid
在AID_ISOLATED_START(99000)
和AID_ISOLATED_END(99999)
之间 - 表明这个服务可以通过
allowed_isolated
来控制是否允许一般的用户进程来使用其服务
- 如果
结语
终于、终于看完了。
书中后面其实还有ashmem 匿名共享内存
的内容(Android自己实现的,在mmap
基础上开发的,基于binder
通信的内存共享)
想了想暂时不作为binder
的笔记内容了,binder
涉及的内容已经很复杂了,不过看完之后也是收获颇丰哈。
看来很必须要写导读(总结)了,哈哈
推荐阅读
-
10年前的三星Galaxy SⅡ:被开发者成功移植了Android 11系统
-
.NET/ASP.NET Routing路由(深入解析路由系统架构原理)
-
深入了解Android的View工作原理
-
Android 系统启动原理(art 虚拟机)
-
Android 操作系统获取Root权限 原理详细解析
-
破解点评网字体反爬,深入挖掘系统背后的原理
-
小米平板2将在第三季度发布 同时支持Android和Windows 10系统
-
Android中新引进的Google Authenticator验证系统工作原理浅析
-
深入理解Android中View绘制的三大流程
-
深入Android系统(三)Binder-3-原理