全面理解Binder原理
本文基于 Android 9.0.0_r3 源码。
如果不了解进程间通信,请先看文末【7. 相关知识补充】
一文打通你对Binder通信机制所有疑惑:
- 如何理解Binder通信中的 binder实体、handle句柄?——【1 Binder原理】
- Binder驱动如何维护binder实体和handle的映射?——【3 binder实体与handle引用的维护】:红黑树
- Binder能够传递多大的数据?——【2 Binder传递参数】:多种情况
- Binder的“一次拷贝”是如何实现的?——【4 内存映射】【5.3 一次拷贝】
- client端如何接收到数据?——【5.2 client也通过binder线程接收消息】:APP启动流程onZygoteInit中启动了binder线程
- servicemanager的初始化和app、服务不同?——【6 servicemanager启动流程】:由系统启动
- 进程隔离、IPC通信的介绍与思考。——【7. 相关知识补充】
- 为什么选择了 Binder机制来做IPC通信 ——【7.5 为什么要用Binder】
需要注意的有:
- 内存映射部分,很多文章都谈到在mmap()的时候,就做了内核空间到物理内存的映射和用户空间到物理内存的映射。但在Android 9.0.0_r3 的源码中,mmap()仅做了用户空间到物理内存的映射。前者映射发生在 binder_transaction() 中,而且不是真正的内存映射,只是一个单向地址引用(t->buffer = 申请到的可用buffer的地址)。在【5.3】中有详细讨论。
- Android官方宣称只有“一次拷贝”,为什么源码出现了多次 copy_from_user()和 copy_to_user()?在【5.3 一次拷贝】中有详细讨论,简而言之其实是只有“一次数据拷贝”,其他的拷贝都是一些附加信息,并不是对数据本身做拷贝。
1. Binder原理
binder的service_server可以向service_client提供service服务,但反过来不行。所以binder service其实是单向的,只有service_server端才能提供service函数,且函数只能在service_server端运行.
大部分情况下:service_server端提供的一组IPC服务本地函数,就是binder对象。
service在main中进行服务注册,例如mediaserver注册
/frameworks/av/media/mediaserver/main_mediaserver.cpp:
int main(int argc __unused, char **argv __unused)
{
MediaPlayerService::instantiate();
}
↓
/frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp:
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
String16("media.player"), new MediaPlayerService());
}
service_server提供了一组可以在server本地运行的函数,即binder对象:
/frameworks/av/media/libmedia/IMediaPlayerService.cpp:
status_t BnMediaPlayerService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch (code) {
...
case CREATE_MEDIA_RECORDER: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
//从data中读取数据
const String16 opPackageName = data.readString16();
//处理数据
sp<IMediaRecorder> recorder = createMediaRecorder(opPackageName);
//写入返回的内容
reply->writeStrongBinder(IInterface::asBinder(recorder));
return NO_ERROR;
} break;
...
}
}
在service_client端可以通过handle来引用这个binder对象,还封装了一系列与之对应的函数来组织数据。但是这些函数实际上是通讯用的,函数的实际功能并不能在client本地执行:
/frameworks/av/media/libmedia/IMediaPlayerService.cpp:
class BpMediaPlayerService: public BpInterface<IMediaPlayerService>
{
public:
...
virtual sp<IMediaRecorder> createMediaRecorder(const String16 &opPackageName)
{
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
data.writeString16(opPackageName);
//调用BpBinder::transact()来传数据
remote()->transact(CREATE_MEDIA_RECORDER, data, &reply);
return interface_cast<IMediaRecorder>(reply.readStrongBinder());
}
...
};
binder对象、handle的规则:
- service_server类的进程只有binder对象,没有handle(除了handle0),因为它所有操作都必须本地执行,引用远程对象毫无意义;
- service_client类的进程只有handle,没有binder对象,因为它需要远程执行service不需要本地执行;
- service_mannager进程同时有binder对象和handle,它本地binder对象的作用就是操作所有其他进程的handle;
这些引用的关系和维护,在【3】 中讲到。
2. 传递的数据格式/ Binder传递的参数
binder使用parcel方式来打包函数参数和返回值。parcel可以用来传递几种类型的数据:
- 普通类型的少量数据;
- binder对象/handle(struct flat_binder_object);
- fd(struct binder_fd_object);
2.1 普通类型的少量数据
2.2 binder对象/handle
2.1和2.2的格式都一样:
data0/data1/…这样的是普通类型的数据放在Parcel的前面;
后面接着 binder_obj0/binder_obj1/…这样的是binder对象,也可能是handle,他们存储的形式是一样的,binder对象和handle共用结构体struct flat_binder_object
binder_obj0 offset/binder_obj1 offset/… 指出了binder对象在parcel包中的偏移。
需要注意的是,parcel打包的数据经过Binder驱动时【binder_transaction()】,binder实体这部分数据会被转换:
- 从service_client端发送到service_server端,parcel中的handle会被转为binder实体。(client端通过handle向server端发送数据时会发生)
- 从service_server端发送到service_manager端,parcel中的binder实体会被转为handle实体。(server端向servicemanager注册服务是会发生)
- 从service_manager发送到service_client端,parcel中的handle实体不会被转变。(client端向servicemanager获取服务,servicemanager只会传递binder对象的引用,即handle)
注意,上述省略了增加binder_ref挂载到红黑树的操作说明,只说了相关核心重要部分。
2.3 传递文件句柄fd
传输fd的意义何在呢?由于Binder内存映射的空间最大只允许4M,binder的两个进程间需要传输大量的数据时就只能使用传递文件句柄fd方式。例如:图像声音数据、或者是一个对象。可以在匿名共享内存(Ashmem)中创建一块区域,源进程会得到一个相应的fd,再把这个fd使用binder传递给目的进程,就可以共享数据了。
注意对象序列化的Parcelable和binder的parcel数据封装不是一回事,尽管他们原理上很相似。binder并没有提供对象Parcelable的接口,如果我们要跨进程传输对象,只能把对象序列化(Parcelable)到匿名共享内存中,再把对应fd通过binder传输给目的进程。
binder驱动给每个进程分配最多4M的buffer空间(一般从Zygote孵化出来的APP默认分配 1M-8K大小,servicemanager默认分配128K).
当然可以突破这个 1M-8K 的限制,可以自己手动调用open和mmap即可:
int main(int argc,char **argv){ ... bs = binder_open("/dev/binder",【自定义大小】); }
但是还是无法突破 binder_mmap() 中 SM_4M 的限制
如果还要再深究,其实binder_mmap中害设置了最大值的另外设置:
static int binder_mmap(...){ proc->free_async_space = proc->buffer_size/2; }
对于oneway和非oneway来说:
手写mmap初始化binder服务 ProcessState初始化Binder服务 oneway 4M/2 (1M-8K)/2 非oneway 4M 1M-8K 一般情况下,Intent传输数据的上限是1M,因为 Intent 传输数据的机制中,用到了Binder。Intent 中的数据,会被作为 Parcel被存储在 Binder的事务缓冲区(Binder transaction buffer)中的对象进行传输。而 1M 并不是安全的上限,还是推荐不要通过Intent传递太大的数据。
解决办法:
- 减少传输数据量
- Intent 通过绑定一个 Bundle 来传输,这个可以超过 1M,不过也不能过大
- 通过内存共享
- 通过文件共享,如这里说到的 binder通信中进行传输文件句柄fd
这里不做 Bundle 的知识补充
3. binder对象关系的维护
- service_server进程在驱动中创建了binder_node节点来保存binder对象,并把binder_node挂在红黑树binder_proc->nodes上
- service_manager和service_client则是对binder对象进行引用,每有一个引用,就创建一个新的binder_ref,它的值是handle,并指向引用的binder对象binder_node,并把这个引用挂载到两颗红黑树上binder_proc->refs_by_nodes/binder_proc->refs_by_desc上。同时,远程引用会增加service_server进程关于binder对象的引用计数。
上述binder对象即binder_node和handle即binder_ref的映射关系,由binder驱动负责:
- 服务注册:service_server把本地binder对象向service_manager注册时,service_server进程本地建立起binder_node,驱动会在service_manager进程中建立起对应的binder_ref引用。这两边建立起的节点根据上面讲的规则挂载到对应的红黑树上。
- 服务获取:service_client根据名字向service_manager查询service。service_manager返回查到的服务的handle,service_client在本地建立起该引用handle的节点binder_ref。
- 服务使用:service_client调用远程service_server的service,Binder驱动判断handle引用是service_server的本地对象,就把handle转换成service_server的binder对象
4. 内存映射关系
binder驱动给每个进程分配最多4M的buffer空间(一般从Zygote孵化出来的APP默认分配 1M-8K大小,servicemanager默认分配128K),当然可以突破这个限制,在上面我们讨论过。分配的这段buffer空间在内核通过binder_proc->alloc红黑树来管理,同时通过mmap映射到进程用户空间;
其他进程发过来的消息会从其他进程用户空间复制到这里,由于mmap内存映射,本进程访问这部分数据不需要拷贝。这也是为什么Binder驱动只有“一次拷贝”
此外,多个线程会共享本进程的binder buffer。
源码中,我们可以看到servicemanager和由zygote孵化出的app分配的buffer空间是不同的:
servicemanager分配的空间是128K:
frameworks/native/cmds/servicemanager/service_manager.c
int main(){
...
bs = binder_open(driver,128*1024);//这也给我们手动突破1M-8K的思路,就是自己调用binder_open,并传入大于1M的值,不过会被后续的4M限制。
}
binder_open,发现mmap映射的空间就是上面servicemanager传入的128K大小。
frameworks/native/cmds/servicemanager/binder.c
struct binder_state *binder_open(const char* driver, size_t mapsize){
...
bs->fd = open(...);
...
bs->mapped=mmap(...,mapsize,....);
}
由zygote孵化出的用户进程,在ProcessState中默认被分配1M-8K:
frameworks/base/cmds/app_process/app_main.cpp
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
//Binder线程最大并发量
#define DEFAULT_MAX_BINDER_THREADS 15
ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver))
, mVMStart(MAP_FAILED)
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mStarvationStartTimeMs(0)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1) {
if (mDriverFD>0) {
//映射一块 1M-8K 大小的物理内存
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
opened.value(), 0);
//...
}
}
5. service_client 向 service_server 发送数据
这里只画出了native层binder通讯的部分重要过程,还有一些细节在后续的图中表现,这张图主要表示了以下几个点:
- binder在server接收端会创建多个线程,在发送端不会创建专门的线程
- binder在server端的通用对象是BBinder,在client端的通用引用对象是BpBinder。具体service的server端和client端,只需要继承着两个类即可,例如BpXXXService(BpMediaPlayerService)、BnXXXService(BnMediaPlayerService)
- 都说binder的内存映射有两部分:一部分是用户空间虚拟内存到物理内存的映射,一部分是内核空间虚拟内存到物理内存的映射。前者在一开始mmap就映射好了。后者则是在拷贝数据时【binder_transaction()】,向server端申请一块空闲的buffer(物理内存的一部分),并用t->buffer指向这块地址,由于binder_transaction t对象是内核空间对象,t->buffer又指向server提供的物理内存,所以被称为内核空间虚拟内存到物理内存的映射。这是个单向映射。
接下来从三个方面进行源码解读:
- server端创建线程接收从Binder驱动中传来的数据
- client端向binder驱动发送消息
- binder驱动将消息转送到server端
5.1 server创建线程接受从Binder驱动中传来的数据
server获取到全局的servicemanager代理对象之后,server就可以注册服务并且创建binder线程处理从Binder驱动中收到的消息。
frameworks/av/media/mediaserver/main_mediaserver.cpp
int main(int argc __unused, char **argv __unused)
{
signal(SIGPIPE, SIG_IGN);
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm(defaultServiceManager());
ALOGI("ServiceManager: %p", sm.get());
InitializeIcuOrDie();
MediaPlayerService::instantiate();
ResourceManagerService::instantiate();
registerExtensions();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
注册服务的代码为:
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp:
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
String16("media.player"), new MediaPlayerService());
}
服务注册完成后,就会开启线程池接受事务:
ProcessState::self()->startThreadPool();
frameworks/native/libs/binder/ProcessState.cpp
void ProcessState::startThreadPool()
{
AutoMutex _l(mLock);
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
↓
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
String8 name = makeBinderThreadName();
/* 新创建一个PoolThread对象
main的意思就是它是一个接收主线程,它不会动态的退出
*/
sp<Thread> t = sp<PoolThread>::make(isMain);
t->run(name.string());
}
}
PoolThread类继承了Thread类,并实现了线程主循环函数:threadLoop()
frameworks/native/libs/binder/ProcessState.cpp
class PoolThread : public Thread
{
public:
explicit PoolThread(bool isMain)
: mIsMain(isMain)
{
}
protected:
virtual bool threadLoop()
{
//线程主循环,进一步调用
IPCThreadState::self()->joinThreadPool(mIsMain);
return false;
}
const bool mIsMain;
};
创建IPCThreadState对象
frameworks/native/libs/binder/IPCThreadState.cpp
IPCThreadState* IPCThreadState::self()
{
if (gHaveTLS) {
restart:
const pthread_key_t k = gTLS;
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if (st) return st;
//创建一个本地线程的IPCThreadState对象
return new IPCThreadState;
}
...
}
随后进入IPCThreadState类的线程主循环函数 joinThreadPool()
frameworks/native/libs/binder/IPCThreadState.cpp
void IPCThreadState::joinThreadPool(bool isMain)
{
//...
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
status_t result;
//主循环
do {
//告诉binder驱动,我要监听并处理事务
result = getAndExecuteCommand();
//...
if(result == TIMED_OUT && !isMain) {
break;
}
} while (result != -ECONNREFUSED && result != -EBADF);
//如果出现错误,则退出主循环,退出监听,该binder线程执行结束
mOut.writeInt32(BC_EXIT_LOOPER);
talkWithDriver(false);
}
status_t IPCThreadState::getAndExecuteCommand()
{
status_t result;
int32_t cmd;
/**
* 和 binder驱动交互
* 把 mOut 中的数据发给 binder 驱动
* 把从驱动中接收到的数据放到 mIn
*/
//告诉binder驱动,我要监听数据
result = talkWithDriver();
if (result >= NO_ERROR) {
size_t IN = mIn.dataAvail();
if (IN < sizeof(int32_t)) return result;
//读出接收数据中的cmd
cmd = mIn.readInt32();
//...
//执行cmd
result = executeCommand(cmd);
//...
}
return result;
}
server端执行接收到的命令,我们只需要关注其中BR_TRANSACTION命令的处理:
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;
switch ((uint32_t)cmd) {
//...
case BR_TRANSACTION:
{
binder_transaction_data tr;
//mIn为从驱动中读到的数据,是tr,里面有数据指针,指向映射内存空间的地址
result = mIn.read(&tr, sizeof(tr));
//通过地址指针,直接访问物理内存中的数据
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);
//准备返回的数据,打包在parcel结构中
Parcel reply;
status_t error;
if (tr.target.ptr) {
if (reinterpret_cast<RefBase::weakref_type*>(
tr.target.ptr)->attemptIncStrong(this)) {
//如果target是一个合法的本地对象,那么server端就可以执行Binder对象的代码。把tr.cookie(Binder对象)转换成BBinder对象,并调用BBinder->transact()来处理数据,之后就去到了类如BnMediaPlayerService->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 {
error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
}
if ((tr.flags & TF_ONE_WAY) == 0) {
//如果需要返回数据给发送方,即需要返回数据给client端
if (error < NO_ERROR) reply.setError(error);
sendReply(reply, 0);
} else {
LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
}
//...
break;
}
if (result != NO_ERROR) {
mLastError = result;
}
return result;
}
其中我们注意到server进程收到消息后,通过BBinder->transact()进行数据处理。BBinder是一个标准的通用binder对象,它的transact()函数会被具体的service子类重写,比如系统自带的Native层的mediaplayerservice,或者我们自定义实现的应用层Service:
frameworks/native/libs/binder/Binder.cpp
status_t BBinder::onTransact(//需要被子重写!
uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/)
{
switch (code) {
case INTERFACE_TRANSACTION:
reply->writeString16(getInterfaceDescriptor());
return NO_ERROR;
default:
return UNKNOWN_TRANSACTION;
}
}
例如native层的BnMediaPlayerService是负责具体实现的子类之一,最后会调用进BnMediaPlayerService类的onTransact()函数中:
frameworks/av/media/libmedia/IMediaPlayerService.cpp
status_t BnMediaPlayerService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch (code) {
case CREATE: {
//检查这个接口是否合法
CHECK_INTERFACE(IMediaPlayerService, data, reply);
//client端是谁
sp<IMediaPlayerClient> client =
interface_cast<IMediaPlayerClient>(data.readStrongBinder());
//请求数据
audio_session_t audioSessionId = (audio_session_t) data.readInt32();
//处理数据
sp<IMediaPlayer> player = create(client, audioSessionId);
//把返回值写回reply中
reply->writeStrongBinder(IInterface::asBinder(player));
return NO_ERROR;
} break;
case CREATE_MEDIA_RECORDER: {
//类似上述流程,不做展示
...
} break;
default:
//没有合适的,交给父类默认处理
return BBinder::onTransact(code, data, reply, flags);
}
}
5.2 client端向binder驱动发送消息
在说发送消息之前,我们要补充说明,client端虽然用作发送消息,但它也可以接收消息,接收消息的场景主要在:从servicemanager获取目标服务的handle、从目标服务获取返回消息。client端接收消息也是使用binder线程,不断监听从binder驱动发来的消息。
我们知道,在App被Zygote fork出来后,会进入到ZygoteInit.java,其中进行初始化操作,其中就实例化了ProcessState。不论是client端还是server端,实例化ProcessState之后,都会开启一个binder线程用于循环监听binder发来的消息!【joinThreadPool()】
ZygoteInit.java
public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
...
RuntimeInit.commonInit();//初始化运行环境
ZygoteInit.nativeZygoteInit();//启动Binder线程池进程
//调用程序入口函数
return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
其中nativeZygoteInit()会通过jni进入到AndroidRuntime.cpp
frameworks/base/core/jni/AndroidRuntime.cpp
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
gCurRuntime->onZygoteInit();
}
随后进入到app_main.cpp进行ProcessState的初始化,以及打开binder线程池等待接受数据,这里线程池接受数据的逻辑与server端一样,不做赘述
frameworks/base/cmds/app_process/app_main.cpp
virtual void onZygoteInit()
{
sp<ProcessState> proc = ProcessState::self();
ALOGV("App process: starting thread pool.\n");
proc->startThreadPool();
}
接下来我们具体讨论 client 是如何发送消息的:
client端发起通讯的起点:在拿到服务的远程代理BpXXXBinder后,client端需要通过binder进行IPC通信,可以调用binder远端代理类BpBinder,先发送数据到binder驱动
frameworks/native/libs/binder/BpBinder.cpp:
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类的相关方法进行与binder驱动通信
system/libhwbinder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
err = writeTransactionData(BC_TRANSACTION_SG, flags, handle, code, data, nullptr);
if((flags & TF_ONE_WAY) == 0){
if(reply){
err = waitForResponse(reply);
}else{
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
}else{
err = waitForResponse(nulptr,nullptr);
}
}
//在 waitForResponse 中调用 talkWithDriver 进行通信。
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
while(1){
//通过 talkWithDriver 发起通信,通信的数据来自全局变量 mOut,如果有收到回复,回复数据将存在 mIn 中。
if ((err=talkWithDriver()) < NO_ERROR) break;
//如果talkWithDriver()正常返回,进入后续处理
switch(cmd){
case BR_TRANSACTION_COPLETE:...
break;
case BR_REPLY:...
goto finish;
//...
default:
err=executeCommand(cmd);
//...
}
}
}
其中,writeTransactionData()对传入的数据进行了处理,封装为了 binder_transaction_data类型对象,并连同cmd控制命令写入Parcel类型的mOut
system/libhwbinder/IPCThreadState.cpp
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr_sg.transaction_data.data_size = data.ipcDataSize();
tr_sg.transaction_data.data.ptr.buffer = data.ipcData();
tr_sg.transaction_data.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr_sg.transaction_data.data.ptr.offsets = data.ipcObjects();
tr_sg.buffers_size = data.ipcBufferSize();
}else{
//...
}
//将 cmd 控制命令,以及 data 数据的信息、data真实数据的地址指针(而不是真的那么一大块数据,只是指针而已) 放在 mOut。
mOut.writeInt32(cmd);
mOut.write(&tr_sg, sizeof(tr_sg));
return NO_ERROR;
}
mOut在writeTransactionData()中打包好后,由waitForResponse()中的talkWithDriver对binder驱动发起通讯,
system/libhwbinder/IPCThreadState.cpp
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
//用于包装传入的数据
binder_write_read bwr;
//传入数据的大小
bwr.write_size = outAvail;
//把mOut的数据存入 write_buffer 中,注意到 mOut中的tr.data.ptr.buffer存的不是真实数据内容,而是真实数据的内存地址指针
bwr.write_buffer = (uintptr_t)mOut.data();
// This is what we'll read.
if (doReceive && needRead) {
//接收缓冲区信息的填充,如果以后收到数据,就直接填在 mIn 中(例如 servicemanager 就会进入到这一步,servicemanager 开启循环后,只需要监听获取 binder 驱动发来的信息)
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
//没有受到数据时,把read置空,binder只进行write处理
bwr.read_size = 0;
bwr.read_buffer = 0;
}
...
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
//通过 ioctl 不断的读写操作,根 Binder 驱动通信。ioctl最终会系统调用到 binder_ioctl() 函数
ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr);
} while (err == -EINTR);
...
return err;
}
前面封装的 binder_transaction_data 对象实例 tr 中的 data 部分,实际只是指向带传输数据的地址!并不是一整块的数据。这里对它又进行了一次封装,使用的是 binder_write_read 数据结构实例对象 bwr,其中 bwr.write_buffer 保存了指向 mOut.data() 的指针,其实就是指向前面的 binder_transaction_data 对象实例 tr。
调用 ioctl(),然后通过系统调用到 binder_ioctl().
5.3 Binder驱动将Client端发送的数据传递给Server端,并唤醒Server端处理todo队列中的事务。
这里我们主要关注“一次拷贝”。上面那张图只把“一次拷贝”的过程画出来了,其实在 binder 通信中,会有多次的 copy_from_user() 和多次的 copy_to_user(),之所以说 binder只有“一次拷贝”,实际指的是只有“一次数据拷贝”。其他的拷贝都只是小数据的拷贝,例如控制命令、数据地址指针等信息。
请求端(客户端)通过 ioctl,最终通过系统调用,调用到内核层的 binder_ioctl() 进行下一步的处理。
common/drivers/android/binder.c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
//发送端的binder线程,如果接收端有回复消息,将会交给它的todo队列
thread = binder_get_thread(proc);
switch(cmd){
...
case BINDER_WRITE_READ:
ret = binder_ioctl_write_read(filp,cmd,arg,thread);
...
}
return ret;
}
核心是 binder_ioctl_write_read() 函数,用于处理读写的数据。这里大家可以注意一下,APP或者Server进程开启后,会开启一个binder线程,循环获取从binder驱动发来的消息,就是通过binder_ioctl_write_read()中的binder_thread_read()来获取消息。后面我们会谈到:获取消息时,binder线程被唤醒、获取todo事件的内容。这里我们只关注“一次拷贝”在哪里。
common/drivers/android/binder.c
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread *thread)
{
struct binder_proc *proc = filp->private_data;
//arg为发送端用户进程发来的 bwr 的地址
//bwr中包含了 控制命令、真实数据
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
//从用户空间将 bwr 拷贝进内核空间,包装在内核空间的 bwr 中
//这里的拷贝是命令拷贝,并不是真正的传输数据拷贝
if (copy_from_user(&bwr, ubuf, sizeof(bwr))){...}
//write_size大于0,表示用户进程由数据发送到Binder驱动
if(bwr.write_size>0){
//发送数据
ret = binder_thread_write(proc, thread,
bwr.write_buffer,
bwr.write_size,
&bwr.write_consumed);
}
//read_size>0,表示用户进程需要监听Binder驱动发来的数据
if(bwr.read_size>0){
ret = binder_thread_read(proc, thread, bwr.read_buffer,
bwr.read_size,
&bwr.read_consumed,
filp->f_flags & O_NONBLOCK);
}
...
return ret;
}
这里出现了第一个 copy_from_user() 的调用,通过这个调用,会把用户空间的 bwr 指向的数据拷贝到内核空间中。这里 bwr 指向的数据只包含了:一些传入协议,相关控制命令、请求端user data数据的地址指针。所以此时的拷贝并不是真正对user data数据做拷贝,即还没有和服务端被映射的内存关联上。
接下来就进入 binder_thread_write()。其中涉及到数据传入的参数 bwr.write_buffer,它此时还是指向用户空间的 binder_transaction_data 实例对象 tr。
common/drivers/android/binder.c
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)
{
uint32_t cmd;
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
void __user *ptr = buffer + *consumed;
void __user *end = buffer + size;
while (ptr < end && thread->return_error.cmd == BR_OK) {
//拷贝本次处理的命令,是 mOut 的命令部分,BC_XXX。
//mOut包含两部分,一部分是 BC_XXX,另一部分是 tr,即用户空间的 binder_transaction_data 它里面含有指向待传递数据的地址指针
if (get_user(cmd, (uint32_t __user *)ptr));
ptr += sizeof(uint32_t);
switch(cmd){
//...
case BC_TRANSACTION:
case BC_REPLY:{
struct binder_transaction_data tr;
//从用户空间获取 write_buffer 指向的内存数据,它指向的是 tr数据,tr中包含指向真正需要传递的数据的内存地址指针
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
break;
}
}
*consumed = ptr - buffer;
}
return 0;
}
这里出现了第二个 copy_from_user(),这个仍然不是user data数据的拷贝,通过这次拷贝,会把用户空间的 tr,也就是 IPCThreadState.mOut,给拷贝到内核中来。mOut中存放的仍然是user data数据的地址信息,还不是一整块的数据,所以此时仍然没有和服务端被映射的内存关联上,继续向下分析 binder_transaction() 函数,它真正完成了内核空间到“物理内存”的映射,以及数据的拷贝,即“一次拷贝”:
common/drivers/android/binder.c
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply,
binder_size_t extra_buffers_size)
{
//事务
struct binder_transaction *t;
//...
if (!reply && !(tr->flags & TF_ONE_WAY))
t->from = thread;//事务来源与哪个binder线程(可以找到来自哪个进程)
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;
t->is_nested = is_nested;
//...
//分配缓存,这里真正建立 Binder驱动和服务端内存映射到的”物理内存“进行映射。
//方式:从服务端的内存映射的“物理内存”中申请一块空间,用于本次数据传输,t->buffer指向这块“物理内存”实现Binder驱动内核虚拟空间与“物理内存”的映射
t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
tr->offsets_size, extra_buffers_size,
!reply && (t->flags & TF_ONE_WAY), current->tgid);
//...
t->buffer->debug_id = t->debug_id;
t->buffer->transaction = t;//该 binder_buffer 对应的事务
t->buffer->target_node = target_node;//该事务对应的目标 binder实体(只有服务注册后成为binder实体)
t->buffer->clear_on_free = !!(t->flags & TF_CLEAR_BUF);
//分配buffer,拷贝用户数据到 t->buffer 里面
if (binder_alloc_copy_user_to_buffer(
&target_proc->alloc,
t->buffer,
ALIGN(tr->data_size, sizeof(void *)),
(const void __user *)
(uintptr_t)tr->data.ptr.offsets,
tr->offsets_size)) {
//...
}
//分配buffer,拷贝用户数据到 t->buffer里面,根据 data.ptr.offsets,找出传输数据里的 Binder 数据
if (binder_alloc_copy_from_buffer(&target_proc->alloc,
&object_offset,
t->buffer,
buffer_offset,
sizeof(object_offset))) {
//...
}
//...
//如果是注册服务,将会进入到下面代码:
//从data中如果解析出来binder实体,就为其创建binder_node实例节点并在servicemanager中建立binder_ref红黑树节点
for(binder实体){
switch(type){
//如果是binder实体,就创建binder_node实例节点并在servicemanager中建立binder_ref红黑树节点
//如果是binder引用,在servicemanager中找到这个引用对应的binder_ref,并在请求者(客户端)binder_proc下创建服务的引用。
}
}
//...
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
t->work.type = BINDER_WORK_TRANSACTION; //设置事务类型
//将事务入队
if(reply){
binder_enqueue_thread_work(thread, tcomplete);
}else if(!(t->flags & TF_ONE_WAY)){
//把binder_transaction节点插入目标todo队列
binder_enqueue_deffered_thread_work_ilocked(thread,tcomplete);
binder_proc_transaction(t,targt_proc,target_thread);
}
}
在这里完成了内核空间到“物理内存”的映射,此时才真正完成了:Binder驱动内核虚拟地址空间与“物理内存”的映射,再加上之前在创建服务端用户进程时就完成的用户进程虚拟地址空间与“物理内存”的映射,才是真正实现了完整的 Binder 三角内存映射。
binder_transaction()中调用的binder_alloc_copy_user_to_buffers()函数将数据从用户空间拷贝到内核空间
common/drivers/android/binder.c
unsigned long
binder_alloc_copy_user_to_buffer(struct binder_alloc *alloc,
struct binder_buffer *buffer,
binder_size_t buffer_offset,
const void __user *from,
size_t bytes)
{
//...
while (bytes) {
//...
ret = copy_from_user(kptr, from, size);
//...
}
return 0;
}
这里调用了 copy_from_user() 函数。这里才是真正的将用户空间的数据直接拷贝到内核空间中,且该内核空间虚拟地址指向的真正“物理内存”就是接收方用户进程内存映射的“物理内存”,这就是所谓“一次拷贝”的关键点。
将数据拷贝到内核空间,即服务端用户空间映射的物理内存中后,Binder 驱动内核空间会将这次事务放到接收端所在的 todo 队列中,并唤醒服务端目标线程对传输的数据进行下一步处理。也就是接收端之前在 binder_thread_read() 中等待事务,此时被唤醒,可以处理挂在todo队列中的事务了。
5.4 binder驱动通知接收端处理事务
在binder驱动通知接收端处理事务之前,接收端在 binder_thread_read() 中挂起等待事务。binder将事务挂在todo队列后,唤醒等待中的binder线程,将todo队列中的事务数据取出来包装到tr中, 再回传给用户空间的mIn,交给server端的线程处理事件数据。
common/drivers/android/binder.c
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed, int non_block)
{
//...被唤醒....
struct binder_transaction_data_secctx tr;
struct binder_transaction_data *trd = &tr.transaction_data;
struct binder_transaction *t = NULL;
//...
switch(w->type){
//如果队列中获取到的事务是传递数据的,那么就不做什么处理
case BINDER_WORK_TRANSACTION:{
t = container_of(w, struct binder_transaction, work);
}break;
//如果收到的事务是通信完成,那么将该事务内存回收
case BINDER_WORK_TRANSACTION_COMPLETE:{
kfree(w);//释放空间
}break;
//...
}
//如果获取到的事务需要处理,那么就进行数据包装,trd属于tr,可以看成相互之间的数据设置,不用太深究
trd->data_size = t->buffer->data_size;//事务对应的数据的大小
trd->offsets_size = t->buffer->offsets_size;
//得到事务对应的数据访问地址开始处(由于在自己的用户空间有映射,所以可以直接访问)
trd->data.ptr.buffer = (uintptr_t)t->buffer->user_data;
//得到事务对应数据访问地址,结束处
trd->data.ptr.offsets = trd->data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));
tr.secctx = t->security_ctx;
//拷贝返回命令
if (put_user(cmd, (uint32_t __user *)ptr));
//只拷贝了命令对应的固定大小的tr参数,并没有进行 tr.data.ptr.buffer指向数据的数据拷贝(binder通信由于内存映射,不存在第二次数据拷贝)
if (copy_to_user(ptr, &tr, trsize));
}
这里出现了第一个 copy_to_user() 调用,把tr和命令cmd都拷贝到用户空间的 mIn 中。
唤醒并将数据地址指针通过 read_buffer 交给服务端用户空间进程后,服务端就从 binder_thread_read() 函数中返回,回到 binder_ioctl_write_read() 中继续下一步处理。
common/drivers/android/binder.c
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread *thread)
{
...
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);
...
}
//将内核空间中的bwr对象拷贝到用户空间,其里面包含了 tr 对象
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto err;
}
...
}
这里出现了第二个 copy_to_user(),它的功能主要是将内核空间的 bwr 拷贝回用户空间中(这个bwr.read_buffer指向tr,或者说指向IPCThreadState.mIn),至此服务端真正获取到数据地址指针。此后回到接收端的用户空间:
回顾接收端的binder线程循环接受数据的逻辑:
//binder_thread_read()被唤醒后,talkWithDriver()返回,到这里,并继续运行后续读取mIn数据等操作
result = talkWithDriver();
if (result >= NO_ERROR) {
size_t IN = mIn.dataAvail();
if (IN < sizeof(int32_t)) return result;
//读出接收数据中的cmd
cmd = mIn.readInt32();
//...
//执行cmd,executeCommand中也会从mIn读取数据进行处理
result = executeCommand(cmd);
}
接着调用 IPCThreadState::executeCommand 执行下一步的处理逻辑。这部分在前面已经叙述过了,这里就简述一下主要工作:
- 从 mIn 中读取tr,为了拿到 tr.data.ptr.buffer(指向拷贝到内存映射区的数据)
- 通过地址指针,直接访问数据,交给BBinder实例BnXXXService处理
- 可能会有返回消息。
也就是说,并不是直接把这块数据交给server端使用,而是通过 mIn 中获取到的 tr.data.ptr.buffer 指向的地址信息访问数据,。又由于这个数据地址在mmap内存映射的空间内,所以server进程可以直接使用该指针访问数据。
至此,binder通讯完成。
最后补充一下 servicemanager的启动流程,它不是zygote孵化出的进程。
6. servicemanager启动流程
init进程启动时,加载 servicemanager.rc,启动 service_manager.c
service servicemanager /system/bin/servicemanager
...
6.1 servicemanager程序启动main()
servicemanager是一个native层的程序,是一个进程,存在一个main()作为程序入口。它主要做了几件事:
-
binder_open()
打开 binder 驱动,其中使用了mmap()
申请了 128K 的内存空间,用于映射 binder 驱动文件。 -
binder_become_context_manager()
让自己成为系统唯一的上下文管理器,同时也成为了守护进程。 -
binder_loop()
进入无限循环,不断监听并解析 binder 驱动发来的命令 -
ioctl( ,BINDER_WRITE_READ,&bwr)
中的 bwr 是 binder_write_read 变量,其 write_size = 0 时,认为写事件,其read_size=0时,认为读事件。这里的 ioctl() 会调用到 binder_ioctl()。
-
binder_parse()
是 binder_loop() 中进行命令解析的函数,它可能会调用回调函数svcmgr_handler()
进行事件处理,比如:注册服务、获取服务、列出服务信息。
binder_loop(),监听 binder 驱动发来的命令,并交给 binder_parse() 进行处理
frameworks/native/cmds/servicemanager/binder.c
void binder_loop(,,,){
struct binder_write_read bwr;
for(;;){
//...
//与 binder 驱动通信,只负责监听发来的命令
res = ioctl(bs->fd,BINDER_WRITE_READ, &bwr);
//如果收到命令,就通过 binder_parse() 来处理/分发命令
res = binder_parse(bs,0,readbuf,bwr.read_consumed,func);
}
}
binder_parse,根据命令,进行不同的处理
frameworks/native/cmds/servicemanager/binder.c
int binder_parse(...){
switch(cmd){//根据命令,进行不同的处理逻辑
case BR_TRANSCTION:{
struct binder_transaction_data *txn;//收到的数据
//...
struct binder_io msg;//收到的数据
struct binder_io reply;//要发送的数据
bio_init(&reply,...);//初始化reply
bio_init_from_txn(&msg,txn);//从txn.transaction_data解析出 binder_io 的信息,放到msg中
res = func(bs,txn,&msg,&reply);//func是 svcmgr_handler()回调函数
if (txn.transaction_data.flags & TF_ONE_WAY) {
//如果是TF_ONE_WAY处理,则释放txn->data的数据
binder_free_buffer(bs, txn.transaction_data.data.ptr.buffer);
} else {
//如果不是TF_ONE_WAY处理,给binder驱动回复数据
binder_send_reply(bs, &reply, txn.transaction_data.data.ptr.buffer, res);
}
}
}
}
**svcmgr_handler()**被回调处理 BR_TRANSACTION 事务
frameworks/native/cmds/servicemanager/service_manager.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_CHECK_SERVICE:
...
//...
}
}
struct binder_io用来读取binder传递的数据
struct binder_io
{
char *data; //指向数据 读/写的指针
binder_size_t *offs; //数据偏移量
size_t data_avail; //数据缓冲区中可用的字节数
size_t offs_avail; //偏移数组中可用的条目
char *data0; //数据缓冲区的起始地址
binder_size_t *offs0; //缓冲区偏移量的起始位置
uint32_t flags;
uint32_t unused;
};
6.2 binder_loop()负责处理的命令
主要有三个命令:
- BR_TRANSACTION: 事务处理,解析 binder_transaction_data 的数据,调用回调函数
svcmgr_handler()
进行服务的注册、获取等操作。 - BR_REPLY: 向Client端进行消息回复。
- BR_DEAD_BINDER: 死亡通知
6.3 回调函数svcmgr_handler() 进行命令处理
在BR_TRANSACTION的命令解析后,就把binder_transaction_data_secctx的数据传给回调函数svcmgr_handler()进行处理。主要进行三个操作:
do_find_service()
获取服务,返回查询到的目标服务的 svcinfo 信息。do_add_service()
添加服务。svc_can_list()
扫描服务列表。
6.4 ServiceManager 用 svclist 维护 service 信息。
其中的 handle 变量记录的是 service 对应的 binder 句柄值,通过 handle 可以最终找到远端的 service 实体即binder对象。name 是服务的名字。
1.5 注册服务
svcmgr_handler() 处理注册服务的核心逻辑:
- 先从msg中获取handle,
- 如果全局变量 svclist 存在该 handle,就更新该服务的 handle 信息。
- 如果 svclist 不存在该服务,就申请一个 svcinfo 空间,把服务名、handle 等信息存入其中,并加入 svclist 链表中。
1.6 查找服务
svcmgr_handler() 处理查找服务的核心逻辑:
- 从 svclist 中根据名字查到一个 svcinfo 的结构,并返回服务的 handle
- 将 handle 放入 reply 变量的 data 中
- 最后在 binder_parser() 中调用 binder_send_reply(),以 BC_REPLY 命令,通过 ioctl 发送给 binder 驱动,最后转到 client 进程。
**svcmgr_handler()**收到查找服务的命令,进行处理
frameworks/native/cmds/servicemanager/service_manager.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_CHECK_SERVICE:
s=bio_get_string16(msg,&len);//获取服务名和长度
handle = do_find_service(s,发起人消息);//根据服务名查询服务
if(!handle)break;//如果没找到,handle=0,退出处理
bio_put_ref(reply,handle);//保存得到的handle句柄到reply中
//...
}
}
**do_find_services()**查询服务并返回句柄handle
frameworks/native/cmds/servicemanager/service_manager.c
uint32_t do_find_service(s,len,uid,spid){
struct svcinfo *si = find_svc(s,len);
//找不到或出错返回0
return si->handle;
}
//遍历 svclist,如果查到,返回svcinfo,没查到返回空
struct svcinfo *find_svc(const uint16_t *s16, size_t len)
{
struct svcinfo *si;
for (si = svclist; si; si = si->next) {
return si;
}
return NULL;
}
bio_put_ref() :如果返回的handle不为0,即查找到服务的handle后,存入reply中,其中,存入的type为HANDLE:BINDER_TYPE_HANDLE,也就是通过reply给client端返回handle
void bio_put_ref(struct binder_io *bio, uint32_t handle)
{
//...初始化obj
obj->hdr.type = BINDER_TYPE_HANDLE;
obj->handle = handle;
obj->cookie = 0;
}
binder_parse():回调svcmgr_handler完成后,回到 binder_parse继续工作,txn为 binder_loop 读到的命令。
frameworks/native/cmds/servicemanager/binder.c
int binder_parse(...){
switch(cmd){//根据命令,进行不同的处理逻辑
case BR_TRANSCTION:{
...
res = func(bs,txn,&msg,&reply);//func是 svcmgr_handler()回调函数,返回的res是handle
//handle>0会进入到 binder_send_reply()方法进行消息回复
binder_send_reply(bs, &reply, txn.transaction_data.data.ptr.buffer, res);
}
}
}
binder_send_reply() 进行消息回复,将 BC_FREE_BUFFER 和 BC_REPLY 命令发给 Binder 驱动,向 Client 端发送 reply,其中 data 数据区中保存的 TYPE 为 HANDLE。
frameworks/native/cmds/servicemanager/binder.c
void binder_send_reply(struct binder_state *bs,
struct binder_io *reply,
binder_uintptr_t buffer_to_free,
int status){
struct {
uint32_t cmd_free;
binder_uintptr_t buffer;
uint32_t cmd_reply;
struct binder_transaction_data txn;
} __attribute__((packed)) data;
//将消息都装到 data 中去
//...
//buffer_to_free是binder_loop()收到的命令中携带的数据
data.buffer = buffer_to_free;
data.cmd_reply = BC_REPLY; // reply命令
//...
if(status){//handle>0
data.txn.data.ptr.buffer=&status;//把 handle实体作为传递的数据
}
//向Binder驱动通信
binder_write(bs, &data, sizeof(data));
}
binder_write() 实际就是将消息写入 binder_write_read 结构中,并通过 ioctl 给 binder驱动发送消息。data消息被存在 binder_write_read 中。
frameworks/native/cmds/servicemanager/binder.c
int binder_write(struct binder_state *bs, void *data, size_t len)
{
struct binder_write_read bwr;
int res;
bwr.write_size = len;//write_size>0 未来会进入 binder_thread_write() 进行:binder驱动从bwr读取数据
bwr.write_consumed = 0;
bwr.write_buffer = (uintptr_t) data;//这就是上述 binder驱动要从 bwr中读取的数据
bwr.read_size = 0;
bwr.read_consumed = 0;
bwr.read_buffer = 0;
//通过ioctl,调用到 binder_ioctl 向 binder 驱动通信,命令为 BINDER_WRITE_READ,即数据传输命令
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
return res;
}
再往后就是之前谈过的,请求的client端变为了server端,其binder线程收取servicemanager发来的返回消息。最终获取到服务的代理/handle/服务的引用binder_ref。
7. 相关知识补充
7.1 进程隔离
进程隔离简单的说就是 Linux 操作系统设计的一种机制,使进程之间不能共享数据,保持各自数据的独立性,即A进程不能访问B进程数据,同理B进程也不能访问A进程数据。通过虚拟内存技术,达到 Linux 进程中数据不能共享,从而保持独立的功能。所以,Linux 进程之间要进行数据交互就得采用特殊的通信机制,即 IPC 通信!而 Android 的应用程序作为特殊的 Linxu 进程也遵循了这一原则。
7.2 虚拟内存与物理内存
虚拟内存是操作系统内存管理的一种技术,它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换(缺页)。
由于局部性定理,一个程序不需要完全被装载进物理内存中就可以运行,但一个程序想要运行就要申请到整个程序大小的地址空间。这样的矛盾,为了只加载使用到的程序段,节省物理内存的空间,我们引入了虚拟内存。当需要用到哪一块数据,如果虚拟内存的页表上发现没有加载过这块数据(被称为缺页),就从磁盘中将该块调入内存,并记录在虚拟内存的页表中。操作系统也会将暂时不会用到的内存块从物理内存中抛弃。
也可以把虚拟内存理解为缓存,物理内存理解为内存,程序正在运行的是缓存中的程序段(物理块),当运行的程序段不在缓存中(类似虚拟内存的缺页情况),就会去内存/磁盘中找,并加入缓存提供给程序使用。
7.3 内核空间和用户空间
为保证内核的安全,操作系统将虚拟地址空间划分为了两部分,一部分是内核空间,一部分是用户空间。
关于进程空间,需要注意:
- 内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟内存中的(当然肯定会在实际使用中映射到实际的物理内存上面去的)。
- 为了保证系统的安全,用户空间和内核空间是天然隔离的。用户空间想要访问内核空间,必须进行系统调用,陷入到内核态,才可以访问。
7.4 传统IPC通信方式
IPC(Inter-Process Communication),进程间通信
1.管道、队列:需要两次内存拷贝
发送方缓存区–memcpy–>内核缓存区 --memcpy–>接收方缓存区 ,存在两次拷贝
2.共享内存,无需拷贝,但控制复杂,难以使用
3.Socket也是两次内存拷贝,但 传输效率低,开销大,主要用在跨网络的进程间通信、本机上进程间的低速通信
7.5 为什么要使用Binder?
主要有两点:性能和安全。
1.性能
Binder相对出传统的Socket方式,更加高效;相对传统的共享内存,实现成本大大降低。
IPC内存数据拷贝次数:
2.安全
Socket通信ip地址是客户端手动填入的,都可以进行伪造;**而Binder机制为每个进程分配了UID/PID来作为鉴别身份的标示,从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。**由binder驱动来完成对应身份(使用代理)的数据交付(在【5.3 Binder驱动将数据发送给接收端,并唤醒接收线程处理事务】中有详细讲述)。
3.可以很好地实现Client-Server(CS)架构
对于Android系统,Google想提供一套基于Client-Server的通信方式。当Client需要获取某Server的服务时,只需要Client向Server发送相应的请求,Server收到请求之后进行处理,处理完毕再将反馈内容发送给Client。
但是,目前Linux支持的"传统的管道/消息队列/共享内存/信号量/Socket等"IPC通信手段中,只有Socket是Client-Server的通信方式。但是,Socket主要用于网络间通信以及本机中进程间的低速通信,它的传输效率太低。所以Android系统推出了 Binder通信机制。
参考资料:
https://www.jb51.cc/notes/3283335.html
https://blog.youkuaiyun.com/hello_1995/article/details/128297109
http://kernel.meizu.com/android-binder.html【非常详细】
在线代码地址: