浅谈Android之Binder原理介绍(二)

2.2 ServiceManager

Binder Kernel提供命令BINDER_SET_CONTEXT_MGR来设置bindercontext manager:

1)  BINDER_SET_CONTEXT_MGR在binder kernel对应一个特殊的binder node,说其特殊,是因为这个bindernode没有binder对象相关数据(它本来就没有),只有设置进程(service manager)的进程相关信息,并且只允许一个进程成为binder context manager

2)  Binder kernel为binder contextmanager对应binder node分配固定的handle为0

这样app就可以通过handle:0与service manager建立连接。

Service manager对应的代码目录为:

/system/frameworks/native/cmds/servicemanager

Servicemanager直接使用c编写,直接通过使用ioctl和binder kernel通信,代码这里就不贴了,逻辑很简单,这里做下简单介绍:

1)  程序启动后,先会调用binder_open,这个函数主要是打开/dev/binder设备,然后检查binder version,接着mmap映射共享内存

2)  接着调用binder_become_context_manager->ioctl(bs->fd,BINDER_SET_CONTEXT_MGR, 0)设置为bindercontext manager

3)  最后调用binder_loop(bs, svcmgr_handler)-> ioctl(bs->fd,BINDER_WRITE_READ, &bwr)等待数据进来;svcmgr_handler是一个回调函数地址,它负责transcation data数据的解析。

Service manager定义了四个transaction code:

enum {

   /* Must match definitions in IBinder.h and IServiceManager.h */

   PING_TRANSACTION  =B_PACK_CHARS('_','P','N','G'),

   SVC_MGR_GET_SERVICE = 1,

   SVC_MGR_CHECK_SERVICE,

   SVC_MGR_ADD_SERVICE,

   SVC_MGR_LIST_SERVICES,

};

每一个被添加到servicemanager的service(native binder)都要有对应名字,这样,后续就可以通过这个名字来找到这个service对应的handle了。

2.3 基础命令封装

如果要开发一个binder进程间通信程序,我们肯定希望越简单越好,这样我们就可以把更多精力放在业务层面,所以,我们必须要将上头很多ioctl命令进行封装,提供更加简洁易懂的接口给应用开发人员使用。

Android提供ProcessState和IPCThreadState两个类,ProcessState封装一些作用域在整个进程的命令,而IPCThreadState则封装的命令都是作用在单个线程的。

下面看下它们都封装了什么:

类名

函数名

介绍

ProcessState

单例,进程只存在唯一的一个实例,通过静态函数self完成初始化

ProcessState()

构造函数,先调用openDrive打开/dev/binder设备,接着调用mmap

setThreadPoolMaxThreadCount

设置binder线程池最大线程数,对应封装

BINDER_SET_MAX_THREADS

命令

getStrongProxyForHandle(int32_t handle)

传入binder handle,返回基于handle创建的BpBinder对象,同时将已经创建的BpBinder添加到缓存

getContextObject(const sp<IBinder> &)

通过调用

getStrongProxyForHandle(0);

获取ServiceManager对应的BpBinder

startThreadPool

如果未设置binder main looper thread,则创建一条线程并设置为main looper thread

IPCThreadState

同样的,也是通过静态self创建的实例,不同的是,创建的实例都是保存到TLS中的,这就保证了,每个线程只会有唯一的IPCThreadState对象实例

IPCThreadState()

构造函数,主要是将对象自身存入到TLS中,同时初始化Parcel mIn和Parcel mOut用于作为接收和发送数据缓存

joinThreadPool

将当前调用线程注册到binder线程池,并阻塞当前线程

incStrongHandle(int32_t handle)

mOut.writeInt32(BC_ACQUIRE);

talkWithDriver(Boolean receive)

封装BINDER_WRITE_READ命令,如果receive为true,说明有返回数据,mOut和mIn两个缓存数据都要读取,否则,只需要读取mOut缓存数据即可。接着发送到binder kernel。

flushCommands

调用talkWithDriver(false)立即发送mOut缓存中的命令数据

waitForResponse

调用talkwithdriver(true),发送命令数据,并等待返回结果并依次处理

transact(int32_t handle, uint32_t                                 code, const Parcel& data, Parcel*                               reply, uint32_t flags)

发送BC_TRANSACTION命令,如果flags未指定TF_ONE_WAY,则调用waitForResponse等待数据返回

executeCommand

处理返回数据

包括BC_TRANSACTION

ProcessState封装的都是作用在整个进程的一些功能,比如打开并保存设备,mmap,缓存已经创建的handle对应的BpBinder,获取servicemanager,设置binder线程池大小等等

IPCThreadState则封装了binder命令数据发送和接收,由于命令以及关联数据在用户空间和内核空间都会被访问,这就需要在两个空间保持数据序列化规则统一以及做好数据同步,具体的实现是这样的:

1:用户空间使用Parcel序列化数据,内核空间同样使用Parcel规则操作数据

2:由于ioctl是阻塞操作,所以,如果要保持数据同步,最简单的方法,就是开辟两份内存用于保存发送和接收命令数据并保存到TLS(线程本地存储)中即可

这就是IPCThreadState被创建后保存到TLS的原因,同时,它内部保存有两个Parcel类型的

私有变量mOut和mIn,分别用于缓存发送和接收命令以及关联数据。

ProcessState和IPCThreadState怎么用?看代码:

int main(int argc, char** argv)

{

#if defined(HAVE_PTHREADS)

    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

#endif

    char value[PROPERTY_VALUE_MAX];

    property_get("debug.sf.nobootanimation", value, "0");

    int noBootAnimation = atoi(value);

    ALOGI_IF(noBootAnimation,  "boot animation disabled");

    if (!noBootAnimation) {

        sp<ProcessState> proc(ProcessState::self());

        ProcessState::self()->startThreadPool();

        // create the boot animation object

        sp<BootAnimation> boot = new BootAnimation();

        IPCThreadState::self()->joinThreadPool();

    }

    return 0;

}

其实核心的就三行代码,ProcessState::self()完成后,进程binder环境就已经初始化完成,接下去要做的就是创建binder主线程并注册到binder kernel:

ProcessState::self()->startThreadPool();

其实从binder命令的接收角度,到这里就够了,因为后续线程不够用时,binder kernel会

动发命令创建新线程添加到binder 线程池

后续继续调用IPCThreadState::self()->joinThreadPool();将主线程加入binder线程池,主要是为

了阻塞主线程,防止main函数执行结束,进程退出。

Binder环境已经创建ok,接下去就可以创建自己的nativebinder添加到servicemanager中或者从servicemanager拿到目标nativebinder的handle,就可以建立数据通信了。

Binder数据传输是通过BC_TRANSACTION和BR_TRANSACTION两个命令来发送和接收,不同的transaction通过code来区分,这样在实际开发中,我们可能就会定义很多的code来区分各种各样的transaction。

这样明显不方便,需要做进一步的封装。

发布了46 篇原创文章 · 获赞 25 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/zhejiang9/article/details/55095920