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。
这样明显不方便,需要做进一步的封装。