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

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

前言


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


发送消息


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


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还有这么一段代码:


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来处理不同类型的消息,其中:


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

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


总结


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


目录
相关文章
|
16天前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
42 2
|
5月前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
46 2
|
20天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
23 1
|
1月前
|
安全 Java 开发者
在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制
【10月更文挑战第3天】在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制,如`synchronized`关键字、`Lock`接口及其实现类(如`ReentrantLock`),还有原子变量(如`AtomicInteger`)。这些工具可以帮助开发者避免数据不一致、死锁和活锁等问题。通过合理选择和使用这些机制,可以有效管理并发,确保程序稳定运行。例如,`synchronized`可确保同一时间只有一个线程访问共享资源;`Lock`提供更灵活的锁定方式;原子变量则利用硬件指令实现无锁操作。
20 2
|
6月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
2月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
30 0
|
3月前
探索操作系统中的线程同步机制
【8月更文挑战第31天】在多线程编程领域,理解并实现线程同步是至关重要的。本文通过浅显易懂的语言和生动的比喻,带你走进线程同步的世界,从互斥锁到信号量,再到条件变量,逐步揭示它们在协调线程行为中的作用。我们将一起动手实践,用代码示例加深对线程同步机制的理解和应用。
|
4月前
|
调度
【浅入浅出】Qt多线程机制解析:提升程序响应性与并发处理能力
在学习QT线程的时候我们首先要知道的是QT的主线程,也叫GUI线程,意如其名,也就是我们程序的最主要的一个线程,主要负责初始化界面并监听事件循环,并根据事件处理做出界面上的反馈。但是当我们只限于在一个主线程上书写逻辑时碰到了需要一直等待的事件该怎么办?它的加载必定会带着主界面的卡顿,这时候我们就要去使用多线程。
152 6
|
4月前
|
存储 前端开发 Java
(二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器
上篇《初识Java虚拟机》文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再交由执行引擎执行。本文中则会对Java虚拟机的类加载机制以及执行引擎进行全面分析。
|
4月前
|
Java 调度
Java中的线程池机制详解
Java中的线程池机制详解