前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Binder机制中的收发消息及线程池

Binder机制中的收发消息及线程池

作者头像
BennuCTech
发布2022-04-12 14:19:15
1K0
发布2022-04-12 14:19:15
举报
文章被收录于专栏:BennuCTechBennuCTech

前言

在阅读《深入理解android内核设计思想》的有关Binder章节的时候,发现书中有部分问题没有很清晰的描述清楚,所以这篇文章主要是针对收发消息的过程和线程池这两个知识点详细展开一下。注意本篇文章并不是介绍Binder机制,而是针对它的两个小细节深入探讨一下,所以建议大家先详细的阅读《深入理解android内核设计思想》中有关Binder章节后对照阅读本篇文章。

关注本公众号:BennuCTech,发送“Android内核设计思想”获取《深入理解android内核设计思想》电子版。

发送消息

我们知道在Binder机制中,Cleint端是通过BpBinder的transact函数发送请求的,而这个函数是调用IPCThreadState的transact函数:

代码语言:javascript
复制
status_t status = IPCThreadState::self()->transact(mHandle, code, data, replay, flags);

那么这里有几个问题:

这个过程与ProcessState有什么关系?

ProcessState在进程创建时就初始化了,调用open()打开 /dev/binder 驱动设备,再利用 mmap() 映射内核的地址空间。这样整个进程中的线程不必每次请求重新打开驱动。

另外,我们知道BpBinder是由ProcessState创建的。ProcessState中维护了一个全局列表记录所有与Binder对象的相关信息,当在列表中无法找到对应的BpBinder,或者对这个BpBinder没有办法增加一个weak reference时(同样功能的BpBinder),才会重新创建一个对应的BpBinder对象返回。

所以ProcessState是发送过程的基础,准备了发送所需要的一切条件。

IPCThreadState什么时候创建?

IPCThreadState是在需要时创建,简单来说它是线程单例的(通过TLS:Thread Local Storage实现的),而self()函数就是获取这个单例,所以每次发送时如果该线程的没有IPCThreadState实例才创建,否则直接使用。

IPCThreadState发送流程

最关键的几个步骤,首先writeTransactionData仅将数据存入mOut,并未真正发送;然后执行waitForResponse函数,在这个函数里通过while循环,使用talkWithDriver函数发送发送mOut中的消息,并阻塞等待结果。所以真正与binder驱动通信是在talkWithDriver中,在这个函数中就可以看到copy_from_user函数,就是书中提到一次复制的那次复制过程。剩下的就是更底层的部分,我们就先不深入了。

接收消息

在《深入理解android内核设计思想》中,以ServiceManager举例的,它比较特殊,在初始化时会自己开启一个循环来不断的读取消息并处理,即接收过程。但是其他的Binder Service呢?

书中以WindowManagerService为例(这个是基于AIDL的)。它也是在SystemServer.java中启动的,而在这之前,即System_init.cpp还有这么一段代码:

代码语言:javascript
复制
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();

同时书中也详细介绍了ProcessState的startThreadPool源码,其中通过spawnPooledThread(true)创建一个PoolThread线程池对象(这里的true代表是main线程)

PoolThread也是一个Thread子类,在它的run函数中执行了IPCThreadState::self()->joinThreadPool(true)joinThreadPool函数中通过while循环不停的读取消息(talkWithDriver)并处理消息(executeCommand)。

这样整个系统服务进程(SystemServer进程)进入了类似ServiceManager的binder_loop循环了。因为后续再SystemServer中启动的其他服务也运行在这同一个进程中,所以他们就没必要单独与Binder驱动交互。

executeCommand最终会调用BBinder的transact来处理消息,在书中也知道BBinder是Service初始化时创建的。

为什么重复执行joinThreadPool

既然startThreadPool已经执行了joinThreadPool(true)开启了一个循环,为什么System_init中还要执行一次?

这里我猜测是一个保险机制,如果在startThreadPool中第一次启动joinThreadPool(true)循环失败,可以立刻再生成一次。

因为在spawnPooledThread函数中并不是通过start方法启动PoolThread的,而是直接调用它的run函数,因为里面有一个循环,所以其实System_init中后面的那句IPCThreadState::self()->joinThreadPool();是不会执行的,除非在前面出现error的情况。

main线程和非main线程有什么区别?

区别在joinThreadPool函数中,它的参数就是isMain,在循环中有这么一句: if(result == TIMED_OUT && !isMain) { break; } 所以可以看到main线程是不会退出的,而非main线程时可以退出回收的。

线程池有什么作用?线程数有限制么?如何创建新的线程?

如果一个service有太多消息,而main线程while循环正在处理消息,这时候就需要创建新的线程来处理。

这样就需要线程池来管理这些线程,默认是16个,包括main线程,所以可以新建的线程是15个。

而且创建新的线程是Binder驱动来控制的,Binder驱动会判断service是否需要更多的线程来处理,如果需要即service繁忙则通知service创建一个线程。

上面我们知道处理消息是在executeCommand函数中,实际上在这个函数中是通过一个switch-case来处理不同类型的消息,其中:

代码语言:javascript
复制
case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;

所以当收到了BR_SPAWN_LOOPER消息,service就会新建一个非main线程。

总结

通过上面我们也知道,线程池其实是在service端,用于响应处理client端的众多请求。而在client端则每个线程处理自己的请求。而这些都是通过IPCThreadState来进行的(除了ServiceManager,它是特例,而书中一开始以它为例,造成了不小的混淆),所以ProcessState和IPCThreadState在service端和client端都存在,而书中只在client端强调了这两个类,包括插图中也只在client端画出了这两个类,其实也是很混淆的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BennuCTech 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 发送消息
    • 这个过程与ProcessState有什么关系?
      • IPCThreadState什么时候创建?
        • IPCThreadState发送流程
        • 接收消息
          • 为什么重复执行joinThreadPool
            • main线程和非main线程有什么区别?
              • 线程池有什么作用?线程数有限制么?如何创建新的线程?
              • 总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档