Sourcelink

Sourcelink 查看完整档案

惠州编辑  |  填写毕业院校惠州华阳通用电子有限公司  |  小渣渣 编辑 sourcelink.top 编辑
编辑

象征自由的小摩托

个人动态

Sourcelink 发布了文章 · 2019-01-20

Input系统之键值映射

一. 概述

android系统的输入事件来源在linux内核提供的/dev/input的设备节点下, 当该设备下及诶点有数据刻度时,将数据独处并进行一系列的翻译和加工,然后在所有的窗口中寻找合适的接受者,并派发给它;

输入系统总体流程如下(引之深入理解android卷3 ):

clipboard.png

1.1 开发环境

  • 系统: ubuntu 16.04
  • 运行环境: firefly-rk3288
  • android版本: arm-5.1.0

二. 准备工作

2.1 模拟输入事件

为了接下来讲解原理方便, 在这里模拟一个输入设备, 方法-写一个驱动;

  • 驱动源码
/* 参考drivers\input\keyboard\gpio_keys.c */

#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/input.h>

static struct input_dev *input_emulator_dev;

static int input_emulator_init(void)
{
    int i;
    int ret;

    /* 1. 分配一个input_dev结构体 */
    input_emulator_dev = input_allocate_device();

    /* 2. 设置 */
    /* 2.1 能产生哪类事件 */
    set_bit(EV_KEY, input_emulator_dev->evbit);
    set_bit(EV_REP, input_emulator_dev->evbit);
    
    /* 2.2 能产生所有的按键 */
    for (i = 0; i < BITS_TO_LONGS(KEY_CNT); i++)
        input_emulator_dev->keybit[i] = ~0UL;

    /* 2.3 为android构造一些设备信息 */
    input_emulator_dev->name = "smart remote";
    input_emulator_dev->id.bustype = 1;
    input_emulator_dev->id.vendor  = 0x0001;
    input_emulator_dev->id.product = 0x0010;
    input_emulator_dev->id.version = 1;

    /* 3. 注册 */
    ret = input_register_device(input_emulator_dev);
    if (ret < 0) {
        return -1;
    }
    
    return 0;
}

static void input_emulator_exit(void)
{
    input_unregister_device(input_emulator_dev);
    input_free_device(input_emulator_dev);    
}

module_init(input_emulator_init);
module_exit(input_emulator_exit);
MODULE_LICENSE("GPL");
  • makefile
KERN_DIR = /media/sourcelink/Backups/5.CompileCode/firefly3288/kernel

all:
    make -C $(KERN_DIR) M=`pwd` modules 

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m    += smart_remote.o

编译完后,放到板子上加载,使用命令cat /proc/bus/input/devices查看设备加载情况如下:

root@firefly:/data/nfs # ins
insmod    installd  
root@firefly:/data/nfs # insmod smart_remote.ko
root@firefly:/data/nfs # cat /proc/bus/input/devices

....
I: Bus=0001 Vendor=0001 Product=0010 Version=0001
N: Name="smart remote"
P: Phys=
S: Sysfs=/devices/virtual/input/input3
U: Uniq=
H: Handlers=sysrq event3 ddr_freq keychord 
B: PROP=0
B: EV=100003
B: KEY=ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe

如上是我板子加载完驱动的情况,原默认的信息我已经删除, 也可以看下在<font color=red>/dev/input</font>, 节点下多了个<font color=red>event3</font>;

root@firefly:/data/nfs # ls /dev/input/
event0
event1
event2
event3

这样就完成一个输入设备的模拟,接下来介绍两个工具模拟事件的产生;

2.2 工具使用

android系统提供了<font color=red>getevent</font> 与<font color=red>sendevent</font>两个工具供开发从设备节点中读取和写入事件;

  • getevent

语法:

getevent [-opera] [节点路径]

可以使用getevent -help 查看一些具体的operation;

如果不带指定设备节点, 这样会监控所有的设备节点:

root@firefly:/data/nfs # getevent
add device 1: /dev/input/event3
  name:     "smart remote"
add device 2: /dev/input/event2
  name:     "RK_ES8323 Headphone Jack"
add device 3: /dev/input/event1
  name:     "rk29-keypad"
add device 4: /dev/input/event0
  name:     "ff680000.pwm"

现在我将自己的键盘插到开发板上了,使用getevent命令来监控事件;

当我按下并松开键盘上的数字1键获取到的数据如下:

root@firefly:/data/nfs # getevent /dev/input/event4 
0004 0004 0007001e
0001 0002 00000001
0000 0000 00000000
0004 0004 0007001e
0001 0002 00000000
0000 0000 00000000

数据意义依次是: 事件类型, 事件代码, 事件值

事件值的1表示按下, 0表示抬起, 观察数据可以发现每次按下或抬起都会获取到0000 0000 00000000的数据, 表示同步事件,通知此次事件已经结束可以进行处理了;
这里的事件代码是linux端发来的原始数据, 在android端还会进行一次键值布局,下面笔者会讲解这个映射关系;

  • setevent

语法:

setevent [节点路径] [事件类型] [事件代码] [事件值]

现在操作下往设备几点写入一个事件, 打开我开发板的浏览器:

clipboard.png

依次输入如下指令:

sendevent /dev/input/event3 1 2 1
sendevent /dev/input/event3 1 2 0
sendevent /dev/input/event3 0 0 0

效果如下:

clipboard.png

屏幕上出现了一个数字1, 最后发送的0 0 0表示同步事件,通知此次事件已经结束可以进行处理了.

三. 按键布局和键值映射

3.1 Key Layout

按键事件来源于linux内核, 但是在android端对按键的值有重新做一个布局,即将linux key code转换为android key code, 这个布局依赖于个.kl文件, 全称: Key Layout Files;

该文件搜索路径如下:

/odm/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/vendor/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/odm/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/vendor/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl
/odm/usr/keylayout/DEVICE_NAME.kl
/vendor/usr/keylayout/DEVICE_NAME.kl
/system/usr/keylayout/DEVICE_NAME.kl
/data/system/devices/keylayout/DEVICE_NAME.kl
/odm/usr/keylayout/Generic.kl
/vendor/usr/keylayout/Generic.kl
/system/usr/keylayout/Generic.kl
/data/system/devices/keylayout/Generic.kl

从上面路径信息可以看出Key Layout Files文件的命名和厂家信息有关, 具体为供应商id, 产品id和设备名有关;
比如我们的模拟输入设备的需要的.kl文件可以命名为Vendor_0001_Product_0010.klsmart remote.kl;

输入系统在检测到有新设备接入时, 在上述路径查找对应符合规则的.kl文件并加载它,如果没有找到则加载Generic.kl文件;

  • 修改测试

拷贝一个Generic.kl和我们模拟按键设备名字一样, 并加上权限,如果你的板子上没有该目录的话则创建它;

cp /system/usr/keylayout/Generic.kl /data/system/devices/keylayout/smart_remote.kl
chmod 777 /data/system/devices/keylayout/smart_remote.kl

我们打开这个文件看下里面的内容:

key 1     ESCAPE
key 2     1
key 3     2
key 4     3
key 5     4
key 6     5
key 7     6
key 8     7
key 9     8
key 10    9
....

看到这就明白了为什么我们前面输入的键值2,最后在浏览器上看到了1; 我们修改下这个文件:

key 1     ESCAPE
key 2     3
...

卸载驱动, 再重新加载驱动后,再依次输入如下指令:

sendevent /dev/input/event3 1 2 1
sendevent /dev/input/event3 1 2 0
sendevent /dev/input/event3 0 0 0

效果如下:

clipboard.png

这样就达到了输入同样按键却得到不同之的效果了;

3.2 Key Character Map

负责将android key code与修饰符的组合映射到Unicode字符。

/odm/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/vendor/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/system/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/odm/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/vendor/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/system/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX.kcm
/odm/usr/keychars/DEVICE_NAME.kcm
/vendor/usr/keychars/DEVICE_NAME.kcm
/system/usr/keychars/DEVICE_NAME.kcm
/data/system/devices/keychars/DEVICE_NAME.kcm
/odm/usr/keychars/Generic.kcm
/vendor/usr/keychars/Generic.kcm
/system/usr/keychars/Generic.kcm
/data/system/devices/keychars/Generic.kcm
/odm/usr/keychars/Virtual.kcm
/vendor/usr/keychars/Virtual.kcm
/system/usr/keychars/Virtual.kcm
/data/system/devices/keychars/Virtual.kcm

输入系统在创建设备时会在这些路径下查找对应的.kl.kcm文件进行加载, 如果没有找到对应的文件将会加载Generic.klGeneric.kcm文件

  • 修改测试

我们现在根据我们的模拟设备来更改下kcm文件, 操作如下:

mkdir /data/system/devices/keychars
cp /system/usr/keychars/Generic.kcm /data/system/devices/keychars/smart_remote.kcm

打开该文件查看下里面内容:

### Basic QWERTY keys ###

key A {
    label:                              'A'
    base:                               'a'
    shift, capslock:                    'A'
}

key B {
    label:                              'B'
    base:                               'b'
    shift, capslock:                    'B'
}

key C {
    label:                              'C'
    base:                               'c'
    shift, capslock:                    'C'
    alt:                                '\u00e7'
    shift+alt:                          '\u00c7'
}
....

以按键<font color=red>A</font>为例, 查看.kl文件当输入事件代码为30时, 对应到android的key A,
根据.kcm文件知道此时会映射成字符a, 当按下shift键时再按下A会显示字符A;

输入如下指令看下效果:

sendevent /dev/input/event3 1 30 1
sendevent /dev/input/event3 1 30 0
sendevent /dev/input/event3 0 0 0

效果如下:

clipboard.png

为了方便大家查看都使用了文件前面的内容进行修改并演示;

如果想当按下a键时显示字符b, 按下shift+a时显示字符2修改下kcm文件,如下:

key A {
    label:                              'A'
    base:                               'b'
    shift, capslock:                    '2'
}
...

重新卸载驱动并加载驱动, 再次执行:

sendevent /dev/input/event3 1 30 1
sendevent /dev/input/event3 1 30 0
sendevent /dev/input/event3 0 0 0

效果如下:

clipboard.png

现在试下同时按下shift键的效果, 依次输入如下:

sendevent /dev/input/event3 1 42 1
sendevent /dev/input/event3 1 30 1
sendevent /dev/input/event3 1 30 0
sendevent /dev/input/event3 0 0 0

效果如下:

clipboard.png

果然和我们修改的kcm文件映射的字符保持了一致;

3.3 总结

按键事件的转化流程大致如下图:

clipboard.png

如果想个性化定制输入的按键的键值和字符显示只需要修改kl和kcm文件;

查看原文

赞 0 收藏 0 评论 0

Sourcelink 赞了文章 · 2019-01-08

做开发十年,我总结出了这些开发经验

本文由云+社区发表

在一线做了十年的开发,经历了网易、百度、腾讯研究院、MIG 等几个地方,陆续做过 3D 游戏、2D 页游、浏览器、移动端翻译 app 等。

积累了一些感悟。必然有依然幼稚的地方,就当抛砖引玉,聊为笑谈。

一、对于团队而言,流程太重要了

行军打仗,你需要一个向导;如果没有向导,你需要一个地图;如果没有地图,至少要学习李广,找一匹识途的老马;如果你连老马也没有,那最好可以三个臭皮匠好好讨论,力图胜过一个诸葛亮;如果三个臭皮匠连好好讨论也做不到,那就是典型的乌合之众了,最好写代码前,点上三炷香,斟上一杯浊酒,先拜拜菩萨,再拜拜谷歌。

我个人属于性格温和的(程序员大多性格不错),但确实见过少数强势的人,说很多强势的话。在技术上一言而决,一听到任何反对就上升到私人恩怨。这样的风格,到底是刚愎自用,还是胸有成竹,就需要仔细判断了。

为什么说流程重要呢?实际上,如果团队上有孙悟空存在,去西天取经,大概也不需要什么流程,只要方向就可以了。 但作为普通的战士,应该先虑败。找人算命时,应该先听听不好的地方,好的地方就不用听了,总归是好的,不好的地方一定要听,这样才能规避。

这就是我的态度:先悲观一点,划清底线,考虑在这个底线上你该怎么做?

这是我做开发的一个习惯,但这个习惯肯定不适用于买房。

怎么划清底线呢?就是假想团队中没有孙悟空了,光靠你唐玄奘、猪八戒和沙和尚,应该怎么去取经。

这个月走什么地方,遇到山怎么走,遇到河怎么过,遇到路上有妖怪劫道,谁去抵挡。遇到路上有少女要搭救,怎么办?这就是流程,是原则。

我经历过一个流程很混乱的阶段。都是很多年前的事情了,可以拿出来说说,不涉及单个人。

2011年在百度浏览器团队时遇到几件让人影响深刻的事情。 有一次开会,产品拿出 Google 某个产品的 DEMO,里面有一段很酷炫 3D 效果,要求开发加上,只给2天时间,大家目瞪口呆。后续的开发为了赶节奏,导致非常多的 bug ,又为了修改 bug ,leader 将所有的 bug 按照人员平均分配,导致不同模块间的同学相互修改......实在难以想象。好比让做花卷的厨子,去修改西湖醋鱼的味道。

最初的现象是:bug下降的慢,延伸 bug 反而增加,每个人都累的半死,代码风格极其杂乱,为了赶工导致的临时方案层出不穷;

到了中期:人员离职越来也多,代码难以维护,新加的需求与之前的临时方案冲突。

到了后期:想做一些修复,想调整架构,又要保证正常运行,其难度好比在一架飞行的飞机上拆换零件。

然后我也急忙离职了......实在看不到成功的可能性。

后来到了腾讯的团队,感觉流程就规范多了。需求和 bug 有 Tapd 跟踪,产品发布按照节奏,需求提出前会和开发反复讨论可行性,有专门的质量跟踪,有专门的用户反馈,每天知道要做什么,也知道明天要做什么。有产品需求,也有开发需求!这个非常重要。很多团队,都是只有产品需求,开发好像牛一样,耕完地就不管了?

流程其实没那么复杂,就是各司其责+节奏。我们都是“哆瑞咪发梭拉西多”中的一员,各自有各自的责任,然后组合在一起,按照一个节奏跑起来。把该做的事情与该跑的节奏定好。

二、不要炫技,老老实实写代码

网上有一个段子,说有人要用JS实现一个简单的功能,然后朋友给他推荐了几十个库。

真的有必要吗?具体情况具体分析。

居家过日子,你只需要一套普通的工具就可以了;如果你是修车的,你需要一套修车的工具;如果你是光头强,你需要一台伐木机。 吃饭用筷子,用刀叉,都可以,但不要用杀猪刀,不要用丈八长矛!,当然也不能用牙签。

用什么工具,用什么库,问问过来人,多在KM上搜索一下。举个例子:android 上加密,用 SQLChpher就可以了,微信也在用,你当然可以学习;数据库 ORM 思想,用 KM 上推荐的 GreenDAO 就可以了;PC 上 3D 引擎,用OGRE就可以了;小型游戏 DEMO,用 Irrlicht 足够;写 WebGL,用 ThreeJS 足够。

首先想想:一些大库 hold 的住吗,后续发展如何?这些库对安装包的体积影响有多大?有没有调研过同样的产品在用什么?

想清楚了再决定用什么,最好是跟随成功项目的脚步。

三、架构上实用+适用

很喜欢曾国藩的一句话:结硬寨、打呆仗。

一字长蛇阵、八门金锁阵,哪个好?iOS 都是单个进程,微信 Android 版本3.5以前是单进程,3.5以后有独立的网络进程; PC 浏览器的进程架构更加复杂,UI 进程、内核进程、Render 进程,而且还有根据页面多少的进程调节模型。

这些设计都很好,各有各的道理,都适用于当前的产品。所以我的观点是:首先分析当前产品的规模、性质,然后再设计架构。

在当前阶段达到:开发效率+架构的平衡;并向后展望3个月,或者半年左右,看看架构能不能适应。

我做腾讯翻译君时,曾反复犹豫要不要模仿微信加入独立的网络进程。后来逆向了有排在第一二位的竞品,最终采用了现在的主功能单进程模型。

产品规模、人员规模、功能阶段,具体问题具体分析。

四、既要有攻城之力,也要有熬战之气——BUG

产品开发完成后,必然有 bug 。其实开发人员在工作过程中,是有一定的直觉或者心理预判的,即:某个功能模块的质量如何。 这里面的质量包括:可维护性、扩展性、算法渲染效率,还有就是bug与崩溃率。

功能开发完成后,就要开始守城了。

bug,一部分产生是由于架构带来的,例如比较复杂的架构,会导致复杂的实现细节;

但还有很大部分bug,其实是基于如下三个原因产生的:

1 . 对于某个api的不了解,或者对于某个平台,或者 SDK 版本的不了解。 举例而言:android里面非主线程,是不能直接处理UI相关的事情的;JAVA 的内存释放也不是绝对的,相互指向是无法释放的;函数个数是有DEX问题制约的---------------------这些bug的产生,也是开发人员摸索学习的过程,经历过一次就不会再犯了。这是学习广度与熟练度的问题;

2 . 还有一些bug,是由于粗心大意导致的。例如空指针的问题,野指针的问题。在 C 的开发中,野指针的问题,GDI 句柄的释放问题,这些都是严谨的代码需要避免的; 而又一些工具,或者方法是可以规避这些问题的,例如 android中 的利用@ Nullable 和@ NonNull 加强空指针检测等方法;

3 . 还有一些bug,是由于“使用情况各异导致的”。例如:偶现在某个模块crash。这里的本质还是因为逻辑的异常边界没有处理好。例如 android 上的 OOM 问题,还有 PC 上 UI 焦点导致的对象释放问题。这些异常情况,一部分靠测试发现,一部分靠用户反馈,还有一部分就靠自己的异常处理。例如Android中的try catch机制,其实就是遇到异常了,你能纠正错误的机会。

五、自审

每过一段时间,都要站在高空俯视自己,问问:到底是在承担过去,还是在改变未来。

如果之前程序代码质量不好,后面修改问题的时间就会比较多。到了开发的中期,得多问问自己,你在不停的改正以前的错误,还是在做新的东西。 如果修改错误的时间多一点,那就要注意自己的代码质量了!

六、注释

我很喜欢写注释。有大牛说:代码就是最好的注释。 可惜我还没有达到那个程度。所以,我会把注释写的非常清楚。其一:为了自己以后维护的方便; 其二:为了其他人接手的方便。

img

img

这是我在翻译君项目中写注释的方式。1:对于很复杂的逻辑,务必用12345的顺序依次写清楚;2 :对于函数中的某个参数,需要解释为什么要设置这个参数,尤其是公用工具类里面的函数---说清楚参数的背景含义,可以让其他调用者理解的更加清晰。

我一般不用英文写。虽然这样看起来格调很低,但胜在大家都能轻松的看懂。写代码不能太傲娇,写注释也不要太傲娇,目的是让你的搭档或者接手者,更轻松的理解,让她/他少加班。

七、代码结构

代码结构要清晰。有按照功能划分的,有按照 UI 结构划分的。还有公用工具类,有数据管理,有主逻辑控制。不管用哪种思想,有序的代码结构,可以让每个人感觉很干净。好比日本的收纳整理技巧让很多小资推崇,无非就是干净、整洁、便于管理。

而且,还有一个重要的好处:代码结构表现出来的其实是——程序的一个模块逻辑思想——让大家工作在不同的区域。

八、代码风格

代码风格统一!好比一家人,有叫 Tom 的,有叫安东尼的,还有叫流川枫、石破天、圣杰夫拉斯基,无所适从。理论上,看一个函数,就能从名称上区分哪些是成员变量,哪些是局部变量,哪些是全局静态值。

除了命名统一外,还有一行代码最大的宽度,函数的连续调用长度等,头文件的包含风格,也最好有一个约定。类的出现时间,创建人名,最好也加上,看起来没用,但到了追踪问题时,就能看出时间线的好处。

九、安全与逆向

这是针对Android说的,还有PC插件也需要考虑。Android 上首先要防止被别人逆向,我成功逆向并重新打包过有第一位和第二位的竞品。这似乎有点不可思议,但确实做到了。加固+混淆+代码判断,最好都有。

安全上,可以看金刚扫描的漏洞,逐一修改就行。公司很多工具很好用的!

十、开发效率

开发效率可以用这些方式提升:

1 . 构建公用工具类,方便大家使用

2 . 使用开源的一些包,例如 ORM 思想的数据库等

3 . 可以很快的找到问题。开发中,找 bug 的时间,往往是很多的。我用的方法有3个: 使用 try catch; 拦截所有 crash 到我指定的地方;超多的 Log,Log 有统一的控制开关。

4 . 借力:数据上报用灯塔,崩溃上报用 bugly,公司 KM 上很多经验,拿过来用。

十一、安装包体积

1 . TINY 压缩图片

2 . 删除无效的资源文件

十二、UI渲染效率

UI 是用户的第一感觉;UI 快并稳定,第一感觉就不会差太多;管理好内存,基本管理好了一半 crash;管理好 UI,等于管理了人机交互感受。

UI 上的开发是:渲染效率与渲染效果的平衡。

很匆忙的写的,必然有很幼稚的地方,欢迎斧正。

此文已由作者授权腾讯云+社区在各渠道发布

获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

查看原文

赞 148 收藏 87 评论 22

Sourcelink 发布了文章 · 2019-01-02

线程池的C实现

一. 概述

相信大家一直有听过线程池, 但是不一定都知道这到底是个什么东西,是如何实现的;

1.1 为什么要使用线程池?

  • 因为线程的创建和销毁开销较大, 在单位时间内处理大量任务时再创建线程进行处理时间来不及.
  • 可以控制线程数目, 保证不会过度消耗内存.

1.2 线程池适合应用的场合

当一个服务器接受到大量短小线程的请求时, 使用线程池技术是非常合适的, 它可以大大减少线程的创建和销毁次数, 提高服务器的工作效率. 但是线程要求的运行时间比较长, 则不适用.

二. 功能说明

2.1 线程池比喻

  • Setup1: 一个医院,每天面对成千上万的病人,处理方式是:来一个病人找来一个医生处理,处理完了医生也走了。当看病时间较短的时候,医生来去的时间,显得尤为费时了
  • Setup2: 医院引进了线程池的概念。设置门诊,把医生全派出去坐诊,病人来看病先挂号排队,医生根据病人队列顺序依次处理各个病人,这样就省去医生来来去去的时间了。但是,很多时候病人不多,医生却很多导致很多医生空闲浪费水电资源撒
  • Setup3: 医院引进了可伸缩性线程池的概念,如阶段二,但是门诊一开始只派出了部分医生,但是增加了一个领导,病人依旧是排队看病,领导负责协调整个医院的医生。当病人很多医生忙不过来的时候,领导就去多叫几个医生来帮忙;当病人不多医生太多的时候,领导就叫一些医生回家休息去免得浪费医院资源

2.2 线程池功能

线程池一般有以下三个功能:

  • 创建线程池
  • 销毁线程池
  • 添加新任务

以上是对外的三个接口方法;

本次实现的线程池对外有四个接口:

struct sl_thread_pool *sl_thread_pool_create(unsigned int core_td_num, unsigned int max_td_num, int alive_time);
void sl_thread_pool_destory(struct sl_thread_pool *pool);
void sl_thread_pool_destory_now(struct sl_thread_pool *pool);
int sl_thread_pool_push_task(struct sl_thread_pool *pool, void *(*task_fun)(void *arg), void *arg);

在销毁线程的时候我做了个功能细化,分为两种: 一种是立即销毁线程池, 一种是执行完任务队列中的任务再销毁线程池,两种方式都是为阻塞方式;

2.3 API 介绍

2.3.1 创建线程池

struct sl_thread_pool *sl_thread_pool_create(unsigned int core_td_num, unsigned int max_td_num, int alive_time);
core_td_num: 初始化线程数
max_td_num: 最大线程数目(线程数量是动态分配)
alive_time: 线程空闲时存活的时间,单位:毫秒
return: 返回线程池句柄

该接口主要是用于创建一个线程池, 笔者写的该线程池可以动态的伸缩所以加入了最大线程数限制和存活时间.

2.3.2 销毁线程池

void sl_thread_pool_destory(struct sl_thread_pool *pool);

调用该接口时,线程池不会立马被注销而是处理完任务队列中的所有任务才注销;

void sl_thread_pool_destory_now(struct sl_thread_pool *pool);

调用该接口时,立即注销线程池;

2.3.3 添加新任务

int sl_thread_pool_push_task(struct sl_thread_pool *pool, void *(*task_fun)(void *arg), void *arg);

向线程池中添加一个新任务, 形参task_fun为任务的函数指针, arg为函数指针的参数;

三. 实现原理

笔者写的该线程池有两个重要的链表:一个是线程链表,一个是任务链表,还有一个重要的线程:manager线程,用于管理线程的销毁和创建;

3.1 线程池创建

struct sl_thread_pool *sl_thread_pool_create(unsigned int core_td_num, unsigned int max_td_num, int alive_time)
{
    struct sl_thread_pool *pstp = NULL;
    struct sl_thread    *thread = NULL;
    int create_ret = -1;
    pstp = (struct sl_thread_pool*)malloc(sizeof(struct sl_thread_pool));     ①
    if (pstp == NULL) {
        ERR("%s: malloc error for creat pool", __FUNCTION__);
        goto malloc_pool_err;
    }
    
    
    create_ret = sl_create_thread_key(destructor_fun);                        ②
    if (create_ret != 0) {
        ERR("%s: create thread key error", __FUNCTION__);
        goto create_key_err;
    }

    /* 创建manager*/
    create_manager_looper(pstp);
    thread = sl_thread_create(sl_thread_manager_do, pstp);                    ③
    if (thread == NULL) {
        ERR("%s: malloc error for create pool", __FUNCTION__);
        goto create_manager_err;
    } else {
        pstp->thread_manager = thread;
        pthread_setname_np(thread->thread_id, "manager_thread"); 
    }

    /* 初始化线程池链表 */
    list_head_init(&pstp->thread_head);
    list_head_init(&pstp->task_queue.task_head);
    /* 初始化线程池计数 */
    pstp->core_threads_num = core_td_num;
    pstp->max_threads_num = max_td_num;
    pstp->keep_alive_time = alive_time;
    pstp->alive_threads_num = 0;
    pstp->destory = 0;

    pthread_mutex_init(&pstp->thread_mutex, NULL);
    // pthread_cond_init(&pstp->thread_run_signal, NULL);

    /* 初始化工作锁 */
    pthread_mutex_init(&pstp->task_queue.task_mutex, NULL);
    /* 初始化工作队列同步条件 */
    pthread_cond_init(&pstp->task_queue.task_ready_signal, NULL);

    /* 创建核心线程 */
    for (int i = 0; i < pstp->core_threads_num; i++) {                       ④
       thread = sl_thread_create(sl_thread_do, pstp);
       if (thread != NULL) {
            list_add(&pstp->thread_head, &thread->thread_list);   
            pthread_setname_np(thread->thread_id, "core_thread");      
       } else {
           i--;
       }
    }

    /* 等待核心线程创建完成 */
    while (pstp->alive_threads_num != pstp->core_threads_num);

    return pstp;


create_manager_err:
    pthread_key_delete(g_key);

create_key_err:
    free(pstp);

malloc_pool_err:

    return NULL;
}
①: 为线程池分配空间.
②: 创建线程私有数据.
③: 创建manager线程.
④: 创建核心线程,这一定数量的线程是不会被释放的.

线程池的数据结构如下:

struct sl_thread_pool {
    struct list_head thread_head;           /* 线程链表 */
    struct sl_task_queue task_queue;        /* 任务链表 */
    unsigned int core_threads_num;          /* 初始化需要创建的线程数 */
    unsigned int max_threads_num;           /* 创建线程最大上限数 */
    unsigned int alive_threads_num;         /* 当前创建线程数量 */
    pthread_mutex_t thread_mutex; 
    pthread_cond_t  thread_run_signal;      /* 线程run信号 */
    int keep_alive_time;                    /* 空闲线程保持存活时间 unit: ms */
    struct sl_thread *thread_manager;       /* 领导 */
    unsigned int destory;
};

3.2 线程管理

static void *sl_thread_manager_do(void *arg)
{
    struct sl_thread_pool *pstp = (struct sl_thread_pool *)arg;
    int next_poll_time = -1; 
    int keep_alive_time = -1;

    if (pstp == NULL) {
        ERR("%s: pool is NULL", __FUNCTION__);
        return NULL;
    }

    do {
        usleep(100);
    } while(pstp->thread_manager == NULL);

    while (pstp->thread_manager->thread_status != THREAD_QUIT) {
        keep_alive_time = poll_event(pstp, next_poll_time);
        next_poll_time = get_next_poll_time(keep_alive_time);
    }
    INFO("sl_thread_manager_do quit");

    return NULL;
}

manager线程主要是epoll来轮询事件,然后做出相应的处理;主要的事件有三个:

  • 线程挂起事件(空闲)
  • 新增任务事件
  • 注销事件
static int poll_event(struct sl_thread_pool *pool, int time_out)
{
    ...

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int event_count = epoll_wait(g_epoll_fd, eventItems, EPOLL_MAX_EVENTS, time_out);
   
    ...
    
    // Check for poll timeout.
    if (event_count == 0) {                                                ①
        list_for_each(plh, &pstp->thread_head) { 
            pst = sl_list_entry(plh, struct sl_thread, thread_list);
            DEBUG("%s: pstp->alive_threads_num = %d, %ld thread status %s", __FUNCTION__, pstp->alive_threads_num, pst->thread_id, get_status(pst->thread_status));
            if (pstp->alive_threads_num > pstp->core_threads_num) {
                if (pst->thread_status == THREAD_SUPPEND) {
                    pst->thread_status = THREAD_QUIT;
                    sl_notify_all(&pstp->task_queue);
                    delete_when_each(plh);     
                    pthread_join(pst->thread_id, NULL);
                    free(pst);
                    keep_time = 50;  // 50ms再检测一次
                    break;
                }
            } else {
                keep_time = -1;
                break;
            }
        }
        return keep_time;
    }

    // despatch for poll event
    for (int i = 0; i < event_count; i++) {
        fd = eventItems[i].data.fd;
        epoll_events = eventItems[i].events;
        if ((fd == g_wake_read_pip_fd) && (epoll_events & EPOLLIN)) {
            /* thread和task同时来临只处理thread */
            ret_event = sl_get_event();
            switch(ret_event) {
                case EVENT_THREAD:                                        ②
                    DEBUG("EVENT_THREAD");
                    if (pstp->alive_threads_num > pstp->core_threads_num)  {
                        keep_time = pstp->keep_alive_time;             
                    } else {
                        keep_time = -1;
                    }
                    break;

                case EVENT_TASK:                                           ③
                    DEBUG("EVENT_TASK");
                    /* 判断当前线程的消息和当前运行线程比例 */
                    pstq = &pstp->task_queue;
                    if(pstq->num_tasks_alive >= (pstp->alive_threads_num * 2) && (pstp->alive_threads_num <= pstp->max_threads_num)) {
                        /* 创建线程 */
                        pst = sl_thread_create(sl_thread_do, pstp);
                        if (pst != NULL) {
                            list_add(&pstp->thread_head, &pst->thread_list); 
                            pthread_setname_np(pst->thread_id, "other_thread"); 
                        }
                    }
                    break;
                case EVENT_SHUTDOWN:                                       ④
                    DEBUG("EVENT_SHUTDOWN");
                    /* 执行完任务对列中的任务才shutdown */
                    pstp->core_threads_num = 0;
                    pool->destory = 1;
                    break;
                default: break;
            }
        } 
    }

    return keep_time;
}
①: wait超时的处理,一般进入超时状态都是准备注销线程, 线程空闲时则注销.
②: 线程状态变化处理,判断当前线程是否多余核心线程,如果是则设置存活时间为下一轮的wait超时时间.
③: 发送任务事件后,主要是判断当前任务数量,线程池是否处理的过来,否则创建新线程.
④: 注销事件,核心线程数设置为0,等待任务链表中的任务处理完再注销;

事件的轮询主要是借助epoll监控管道的变化实现,想了解的可以详细看下代码;

3.3 任务的执行

static void *sl_thread_do(void *arg)
{
    struct sl_thread_pool *pstp = (struct sl_thread_pool *)arg;
    struct sl_thread_task *pstt = NULL;
    struct sl_task_queue  *pstq = NULL;
    
    if (pstp == NULL) {
        ERR("%s: pool is NULL", __FUNCTION__);
        return NULL;
    }

    pstq = &pstp->task_queue;

    pthread_mutex_lock(&pstp->thread_mutex);
    pstp->alive_threads_num++;
    pthread_mutex_unlock(&pstp->thread_mutex);

    sl_save_thread_self(pstp);

    while (sl_get_thread_self()->thread_status != THREAD_QUIT) {  

        pstt = sl_task_pull(pstq);                                         ①
        if (pstt != NULL) {
            sl_update_thread_status(THREAD_WORKING);
            pstt->task_fun(&pstt->arg);                                    ②
            free(pstt);
        }
    }

    pthread_mutex_lock(&pstp->thread_mutex);
    pstp->alive_threads_num--;
    pthread_mutex_unlock(&pstp->thread_mutex);

    sl_update_thread_status(THREAD_IDLE);                                  

    sl_clear_thread_self();                                                 ③

    INFO("thread_run_task %ld quit, currten threads count %d, currten tasks count %d\n", 
                    pthread_self(), pstp->alive_threads_num, pstq->num_tasks_alive);
    
    return NULL;
}
①: 从任务对列中取出一个任务, 没有则休眠;
②: 执行任务
③: 清除私有数据中存放的值

这在说明一点,用线程的私有数据进行存储, 主要是为了更新线程的状态方便;

3.4 任务添加

int sl_thread_pool_push_task(struct sl_thread_pool *pool, void *(*task_fun)(void *arg), void *arg)
{
    struct sl_task_queue *pstq = NULL;
    struct sl_thread_task *pstt = NULL;

    if (pool == NULL || task_fun == NULL || pool->destory == 1) {
        ERR("%s: pool or task_fun is NULL or is destory status", __FUNCTION__);
        return -1;
    }  

    pstq = &pool->task_queue;

    pstt = (struct sl_thread_task*)malloc(sizeof(struct sl_thread_task));    
    if (pstt == NULL) {
        ERR("%s: malloc error for creat a task", __FUNCTION__);
        return -1;
    }

    pstt->task_fun = task_fun;
    pstt->arg      = arg;

    return sl_task_push(pstq, pstt);
}

该接口主要分配了一个空间初始化传进来的任务,往下看:

static int sl_task_push(struct sl_task_queue *_stq, struct sl_thread_task *new_task)
{
    struct sl_task_queue *pstq = _stq;
    struct sl_thread_task *pstt = new_task;

    if (pstq == NULL || pstt == NULL) {
        ERR("%s: pstq or pstt is NULL", __FUNCTION__);
        return -1;
    }  

    pthread_mutex_lock(&pstq->task_mutex);
    list_add(&pstq->task_head, &pstt->task_list);
    pstq->num_tasks_alive++;
    pthread_mutex_unlock(&pstq->task_mutex);
    sl_notify_one(pstq);
    sl_update_task_queue_info();
    return pstq->num_tasks_alive;
}

将刚才保存的任务添加进任务对列并发送通知;

四. 总结

笔者写这个线程池,主要涉及到这个点有: 同步变量, 锁, 线程私有数据, 管道, epoll和双向队列;

代码已经放到我的github上了: thread pool

查看原文

赞 0 收藏 0 评论 0

Sourcelink 发布了文章 · 2018-12-08

Binder机制情景分析之native层浅析

一. 概述

浅析下android的native层binder的实现,基于android源码5.1,参考mediaService的代码;

1.1 问题

因为前两篇我们用c写过binder的实例,实现了service和client端,也分析了驱动,从上到下好好的看了下binder的实现原理,但是当我自己看到binder的native的源码时,一脸懵逼,封装的太厉害了,脑海中产生了很多疑问,如下:

  • a. native是如何去和ServiceManager通信的?
  • b. service是如何去注册服务的?
  • c. service是如何响应服务的?
  • d. 怎么发送数据给驱动的?

1.2 源码参考

frameworksavincludemediaIMediaPlayerService.h
frameworksavmedialibmediaIMediaPlayerService.cpp
frameworksavmedialibmediaplayerserviceMediaPlayerService.h
frameworksavmedialibmediaplayerserviceMediaPlayerService.cpp
frameworksavmediamediaserverMain_mediaserver.cpp (server, addService)

1.3 模板类

binder的native涉及到两个模板类,分别是BnxxxBpxxx,前者你可以理解为binder native,主要用于service端的服务对象,后者你可以理解为binder proxy,一个代理对象,用service封装好的接口,这要client端调用后,就可以执行对应的服务;对比下c代码实现的client时调用interface_led_on函数,现在不需要自己实现而是service端帮忙封装好,

1.4. 剖析源码

从主函数看起,参考media源码: Main_mediaserver.cpp

int main(int argc __unused, char** argv)
{
      ....
    if (doLog && (childPid = fork()) != 0) {
      .....
    } else {
        ....
        sp<ProcessState> proc(ProcessState::self());            ①
        sp<IServiceManager> sm = defaultServiceManager();       ②
        ....
        MediaPlayerService::instantiate();                      ③
        ....
        ProcessState::self()->startThreadPool();                ④
        IPCThreadState::self()->joinThreadPool();               ⑤
    }
}
①: 获取一个ProcessState实例(详见2.1);
②: 获取一个ServiceManager服务(BpServiceManager对象);
③: 添加媒体播放服务;
④: 创建一个线程池;
⑤: 进入主循环;

二. 打开驱动通道

2.1 ProcessState::self

sp<ProcessState> ProcessState::self()
{
    Mutex::Autolock _l(gProcessMutex);
    if (gProcess != NULL) {
        return gProcess;
    }
    gProcess = new ProcessState;
    return gProcess;
}

可以看出这是个单例,也就是说一个进程只允许一个ProcessState对象被创建;

2.2 ProcessState::ProcessState

ProcessState::ProcessState()
    : mDriverFD(open_driver())                                                                        ①
    , mVMStart(MAP_FAILED)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        // XXX Ideally, there should be a specific define for whether we
        // have mmap (or whether we could possibly have the kernel module
        // availabla).
#if !defined(HAVE_WIN32_IPC)
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);     ②
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
#else
        mDriverFD = -1;
#endif
    }

    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
}
①: 打开binder驱动设备并初始化文件描述符;
②: 建立内存映射;

是不是觉得场景很熟悉,这和我们前面用c写的流程是一样的,先打开一个binder设备再建立内存映射;
不过open_driver函数中它多做一个功能就是设置binder的最大线程数,具体代码我这就不贴了,避免篇幅过长;

三. 获取沟通桥梁

3.1 defaultServiceManager

sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
    
    {
        AutoMutex _l(gDefaultServiceManagerLock);
        while (gDefaultServiceManager == NULL) {
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
            if (gDefaultServiceManager == NULL)
                sleep(1);
        }
    }
    
    return gDefaultServiceManager;
}

这也是个单例,通过interface_cast这个模板函数将getContextObject的值转换成IServiceManager类型,我们先看下getContextObject到底返回了什么给我们?

3.2 ProcessState::getContextObject

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
    return getStrongProxyForHandle(0);
}

通过getStrongProxyForHandle获取一个IBinder对象;

3.3 ProcessState::getStrongProxyForHandle

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;

    AutoMutex _l(mLock);

    handle_entry* e = lookupHandleLocked(handle);                                ①

    if (e != NULL) {

        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {                       ②
            if (handle == 0) {
                Parcel data;
                status_t status = IPCThreadState::self()->transact(
                        0, IBinder::PING_TRANSACTION, data, NULL, 0);
                if (status == DEAD_OBJECT)
                   return NULL;
            }

            b = new BpBinder(handle);                                            ③
            e->binder = b;                                                       ④
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }

    return result;
}
①: 根据handle在mHandleToObject容器中查找是否有对应object,没有则插入一个新的object;
②: 判断刚返回的object中的binder成员是否为空,为空说明是新创建的;
③: 创建一个新的BpBinder对象,根据handle值(详见3.4);
④: 将创建的BpBinder对象填充进object;

注意BpBinder这是个代理对象且它的基类为IBinder,接下来看下这个代理对象到底做了什么?
其实根据这个函数的名字我们也能猜出点东西,结合笔者前面C服务应用时写的,client端在获取到一个handle时,一直都是通过这个handle去与驱动进行交互;
那么我们这个handle是不是也是做同样的功能,转换成面向对象的写法变成了一个代理对象?

3.4 BpBinder::BpBinder

BpBinder::BpBinder(int32_t handle)
    : mHandle(handle)
    , mAlive(1)
    , mObitsSent(0)
    , mObituaries(NULL)
{
    ALOGV("Creating BpBinder %p handle %d\n", this, mHandle);

    extendObjectLifetime(OBJECT_LIFETIME_WEAK);
    IPCThreadState::self()->incWeakHandle(handle);
}

BpBinder的构造函数保留下handle的值并对该handle增加了引用,看到这里可以结合C服务应用篇联想下该类会做什么功能;
当笔者看到这的时候,认为这是一个客户端用来和service交互的代理类,完成驱动交互的工作,类似binder_call函数;

3.5 BpBinder::transact

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;
}

看到BpBinder类中的这个方法,完全验证了我们的思想,但是发现它并不是直接去和驱动交互而是利用IPCThreadState::self()->transact这个方法进行,这个我们后面再分析;

3.6 interface_cast

前面在分析defaultServiceManager時,中有个模板类进行类型转换,前面也分析ProcessState::self()->getContextObject是返回一个Bpbinder对象,现在看下如何转换;

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}

将里面 的INTERFACE替换成IServiceManager,其实是这样的IServiceManager::asInterface(obj); 在IServiceManager类中找了一圈发现没有该方法,最后发现是用宏定义实现,如下:

define DECLARE_META_INTERFACE(INTERFACE)

define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)

3.7 IMPLEMENT_META_INTERFACE

因为DECLARE_META_INTERFACE仅仅只是声明,替换下就可以看出实现的东西了,我们就分析下IMPLEMENT_META_INTERFACE看看是这其中做了什么;

#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
    I##INTERFACE::~I##INTERFACE() { }    

IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager");转换后如下:

    const android::String16 IServiceManager::descriptor("android.os.IServiceManager");             \
    const android::String16&                                            \
            IIServiceManager::getInterfaceDescriptor() const {              \
        return IIServiceManager::descriptor;                                \
    }                                                                   \
    android::sp<IServiceManager> IServiceManager::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<IServiceManager> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<IServiceManager*>(                          \
                obj->queryLocalInterface(                               \
                        IServiceManager::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new BpServiceManager(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    IServiceManager::IServiceManager() { }                                    \
    IServiceManager::~IServiceManager() { }

注意:IServiceManager::asInterface方法,他将传进去的BpBiner对象又用来创建BpServiceManager对象;接下来关注下BpServiceManager的实现;

3.8 BpServiceManager::BpServiceManager

    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }

被用来初始化BpInterface模板了,接着往下看这个模板类做了什么;

3.9 BpInterface::BpInterface

template<typename INTERFACE>
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
    : BpRefBase(remote)
{
}

这里回顾下,怕读者看蒙圈了,传进来的remote变量是我们前面通过getContextObject方法,获取到的handle为0的一个BpBinder对象,该对象是个代理类,是IBinder的派生类,它可以与ServiceManager进行通信的一个代理类;clientservice都可以通过该与ServiceManager进行通信,前者可以通过它获取服务,后者可以通过它注册服务,重点是handle为0,它代表着ServiceManager,不理解的笔者可以看下C服务应用篇;

接下来看下BpRefBase里做了什么;

3.10 BpRefBase::BpRefBase

BpRefBase::BpRefBase(const sp<IBinder>& o)
    : mRemote(o.get()), mRefs(NULL), mState(0)
{
    extendObjectLifetime(OBJECT_LIFETIME_WEAK);

    if (mRemote) {
        mRemote->incStrong(this);           // Removed on first IncStrong().
        mRefs = mRemote->createWeak(this);  // Held for our entire lifetime.
    }
}

注意:mRemote成员,它是个IBinder指针类型,它指向了传进来的BpBinder对象;当笔者看到者时突然顿悟,这样BpServiceManager就被赋予了力量可以通过它去和ServiceManager打交道了;

四. 到达彼岸

PS:defaultServiceManager单例获取到的对象是BpServiceManager对象;
ServiceManager进行沟通的对象,我们已经知道如何获取到了现在看下service如何去注册自己;

4.1 MediaPlayerService::instantiate

void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService(
            String16("media.player"), new MediaPlayerService());
}

通俗点就是调用defaultServiceManager单例中的addService方法,将MediaPlayerService服务注册,接下来看下addService做了什么;

4.2 BpServiceManager::addService

    virtual status_t addService(const String16& name, const sp<IBinder>& service,
            bool allowIsolated)
    {
        Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }

是不是觉得事成相识的感觉,与前面C服务应用篇一样,构造好数据然后发送;remote方法不就是我们前面分析时BpRefBase类中的方法,返回一个BpBinder指针(mRemote);该方法怎么实现后面再说,这里马后炮一下,先讲下writeStrongBinder;

4.3 Parcel::writeStrongBinder

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}


status_t flatten_binder(const sp<ProcessState>& /*proc*/,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;

    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    if (binder != NULL) {
        IBinder *local = binder->localBinder();                        ①
        if (!local) {
            BpBinder *proxy = binder->remoteBinder();                  ②
            if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
            obj.handle = handle;
            obj.cookie = 0;
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local);           ③
        }
    } else {
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    }

    return finish_flatten_binder(binder, obj, out);                    ④
}
①: 获取BBinder对象;
②: 获取一个BpBinder对象;
③: 将获取到BBinder对象保存进cookie;
④: 将该构造好的obj写进数据块中;

注意:形参中传进来的binder是个Service服务对象,通过localBinder方法查看传进来的binder是否有由BBinder对象派生,不是说明这是一个handle,即请求服务,否则为注册服务;与C服务应用篇做对别,有个差别就是这里用到了cookie,将Service对象保存下来了,一开始看到这里不理解,看到后面才明白的;

localBinder方法由BBinder继承后,会返回一个BBinder本身的指针,否则会返回null;

4.4 BnMediaPlayerService::onTransact

我们不关心 new MediaPlayerService()是如何构造的,前面分析到需要通过继承BBinder来判断是注册还是请求服务;
接下来看下媒体类的继承关系;

class MediaPlayerService : public BnMediaPlayerService
    class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
        class BnInterface : public INTERFACE, public BBinder

这样一层层下来,最后发现是通过BnInterface继承了BBinder函数; 继续看BBinder中的localBinder方法:

BBinder* BBinder::localBinder()
{
    return this;
}

果然和我们前面分析的一样,返回了BBinder本身;

其实主角是我们的onTransact方法:

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;

        case DUMP_TRANSACTION: {
            int fd = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String16> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
               args.add(data.readString16());
            }
            return dump(fd, args);
        }

        case SYSPROPS_TRANSACTION: {
            report_sysprop_change();
            return NO_ERROR;
        }

        default:
            return UNKNOWN_TRANSACTION;
    }
}

该方法由派生类复写,用于服务客户端的请求服务,根据客户端传来的不同的code执行不同的服务;

class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
};

BnMediaPlayerService是有复写该方法的;

4.5 IPCThreadState::transact

数据的构造,注册我们都知道怎么实现了,但是怎么发送给驱动我们还没有分析,前面分析时BpBinder::transact的实现是通过IPCThreadState::transact的方法来实现,接下来分析下:

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();

    flags |= TF_ACCEPT_FDS;

    if (err == NO_ERROR) {
        LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
            (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
        err = writeTransact
        ionData(BC_TRANSACTION, flags, handle, code, data, NfaULL);    ①
    }
    
    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }
    
    if ((flags & TF_ONE_WAY) == 0) {

        if (reply) {
            err = waitForResponse(reply);                                               ②
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
              
        IF_LOG_TRANSACTIONS() {
            TextOutput::Bundle _b(alog);
            alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand "
                << handle << ": ";
            if (reply) alog << indent << *reply << dedent << endl;
            else alog << "(none requested)" << endl;
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }
    
    return err;
}
①: 构造发送数据和命令;
②: 等待响应;

笔者刚开始看的时候一脸懵逼,根据方法名,构造数据,等待响应,那发送呢?
一开始以为在构造数据的时候并发出去了,毕竟它用了write;后来才发现秘密在waitForResponse方法中;

4.6 IPCThreadState::waitForResponse

status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags)
{
    status_t err;
    status_t statusBuffer;
    err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer);
    if (err < NO_ERROR) return err;
    
    return waitForResponse(NULL, NULL);
}

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;

    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;                        ①
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;
        
        cmd = mIn.readInt32();                                               ②
        
        IF_LOG_COMMANDS() {
            alog << "Processing waitForResponse Command: "
                << getReturnString(cmd) << endl;
        }

        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
        
        case BR_DEAD_REPLY:
            err = DEAD_OBJECT;
            goto finish;

        case BR_FAILED_REPLY:
            err = FAILED_TRANSACTION;
            goto finish;
        
        case BR_ACQUIRE_RESULT:
            {
                ALOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT");
                const int32_t result = mIn.readInt32();
                if (!acquireResult) continue;
                *acquireResult = result ? NO_ERROR : INVALID_OPERATION;
            }
            goto finish;
        
        case BR_REPLY:
            {
                binder_transaction_data tr;
                err = mIn.read(&tr, sizeof(tr));
                ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
                if (err != NO_ERROR) goto finish;

                if (reply) {
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                        reply->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);
                    } else {
                        err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
                        freeBuffer(NULL,
                            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), this);
                    }
                } else {
                    freeBuffer(NULL,
                        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), this);
                    continue;
                }
            }
            goto finish;

        default:
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }
    }

finish:
    if (err != NO_ERROR) {
        if (acquireResult) *acquireResult = err;
        if (reply) reply->setError(err);
        mLastError = err;
    }
    
    return err;
}
①: 将数据写入驱动
②: 读取驱动返回的数据,根据不同的cmd值做出相应回应;

写进去,接下来就读了说明talkWithDriver是个阻塞方法,点进去看下;

4.7 IPCThreadState::talkWithDriver

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    ...
    
    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        IF_LOG_COMMANDS() {
            alog << "About to read/write, write size = " << mOut.dataSize() << endl;
        }
#if defined(HAVE_ANDROID_OS)
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)                ①
            err = NO_ERROR;
        else
            err = -errno;
#else
        err = INVALID_OPERATION;
#endif
        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }
        IF_LOG_COMMANDS() {
            alog << "Finished read/write, write size = " << mOut.dataSize() << endl;
        }
    } while (err == -EINTR);

   ...
    return err;
}
①: 调用ioctl函数将数据写进驱动;

看到了吗一个do-while阻塞在这里,其实看过驱动就知道调用ioctl时驱动层在没有数据时会休眠;

到这里注册服务就完成了,最后是通过BnMediaPlayerService来提供服务的;

五. 等待服务

服务实现了,也注册了,还有最后一步等待服务请求;

5.1 ProcessState::startThreadPool

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp<Thread> t = new PoolThread(isMain);                            ①
        t->run(name.string());
    }
}

class PoolThread : public Thread
{
public:
    PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }
    
protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);                   ②
        return false;
    }
    
    const bool mIsMain;
};
①: 创建了一个线程池;
②: 也是通过IPCThreadState::self()->joinThreadPool进入循环;

还记得一开始我们设置过线程最大数吗,每个Service是可能同时被多个client请求提供服务的,忙不过来就只能动态创建线程来对应请求服务;
接下来看下joinThreadPool做了什么,大胆的猜测下,是不是进入一个循环,接着调用transact读取数据然后等待数据,来数据后进行解析,然后执行对应的服务;

5.2 IPCThreadState::joinThreadPool

void IPCThreadState::joinThreadPool(bool isMain)
{
    LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());

    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    
    set_sched_policy(mMyThreadId, SP_FOREGROUND);
        
    status_t result;
    do {
        processPendingDerefs();
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();

        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
            ALOGE("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",
                  mProcess->mDriverFD, result);
            abort();
        }
        
        // Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);

    LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n",
        (void*)pthread_self(), getpid(), (void*)result);
    
    mOut.writeInt32(BC_EXIT_LOOPER);
    talkWithDriver(false);
}

打脸了,只进入了一个循环然后调用了getAndExecuteCommand方法,来看看该方法做了什么;

5.3 IPCThreadState::getAndExecuteCommand

status_t IPCThreadState::getAndExecuteCommand()
{
    status_t result;
    int32_t cmd;

    result = talkWithDriver();                                                ①
    if (result >= NO_ERROR) {
        size_t IN = mIn.dataAvail();
        if (IN < sizeof(int32_t)) return result;
        cmd = mIn.readInt32();
        IF_LOG_COMMANDS() {
            alog << "Processing top-level Command: "
                 << getReturnString(cmd) << endl;
        }

        result = executeCommand(cmd);                                         ②

        // After executing the command, ensure that the thread is returned to the
        // foreground cgroup before rejoining the pool.  The driver takes care of
        // restoring the priority, but doesn't do anything with cgroups so we
        // need to take care of that here in userspace.  Note that we do make
        // sure to go in the foreground after executing a transaction, but
        // there are other callbacks into user code that could have changed
        // our group so we want to make absolutely sure it is put back.
        set_sched_policy(mMyThreadId, SP_FOREGROUND);
    }

    return result;
}
①: 前面我们分析过了这是和驱动交互的,它回去读和写;
②: 从读取到的数据中执行相应的code;

5.4 IPCThreadState::executeCommand

status_t IPCThreadState::executeCommand(int32_t cmd)
{
...
    
    case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            ALOG_ASSERT(result == NO_ERROR,
                "Not enough command data for brTRANSACTION");
            if (result != NO_ERROR) break;
            
            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);
            
            const pid_t origPid = mCallingPid;
            const uid_t origUid = mCallingUid;
            const int32_t origStrictModePolicy = mStrictModePolicy;
            const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;

            mCallingPid = tr.sender_pid;
            mCallingUid = tr.sender_euid;
            mLastTransactionBinderFlags = tr.flags;

            int curPrio = getpriority(PRIO_PROCESS, mMyThreadId);
            if (gDisableBackgroundScheduling) {
                if (curPrio > ANDROID_PRIORITY_NORMAL) {
                    // We have inherited a reduced priority from the caller, but do not
                    // want to run in that state in this process.  The driver set our
                    // priority already (though not our scheduling class), so bounce
                    // it back to the default before invoking the transaction.
                    setpriority(PRIO_PROCESS, mMyThreadId, ANDROID_PRIORITY_NORMAL);
                }
            } else {
                if (curPrio >= ANDROID_PRIORITY_BACKGROUND) {
                    // We want to use the inherited priority from the caller.
                    // Ensure this thread is in the background scheduling class,
                    // since the driver won't modify scheduling classes for us.
                    // The scheduling group is reset to default by the caller
                    // once this method returns after the transaction is complete.
                    set_sched_policy(mMyThreadId, SP_BACKGROUND);
                }
            }

            //ALOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid);

            Parcel reply;
            status_t error;

            if (tr.target.ptr) {
                sp<BBinder> b((BBinder*)tr.cookie);
                error = b->transact(tr.code, buffer, &reply, tr.flags);

            } else {
                error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
            }

            //ALOGI("<<<< TRANSACT from pid %d restore pid %d uid %d\n",
            //     mCallingPid, origPid, origUid);
            
            if ((tr.flags & TF_ONE_WAY) == 0) {
                LOG_ONEWAY("Sending reply to %d!", mCallingPid);
                if (error < NO_ERROR) reply.setError(error);
                sendReply(reply, 0);
            } else {
                LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
            }
            
            mCallingPid = origPid;
            mCallingUid = origUid;
            mStrictModePolicy = origStrictModePolicy;
            mLastTransactionBinderFlags = origTransactionBinderFlags;

            IF_LOG_TRANSACTIONS() {
                TextOutput::Bundle _b(alog);
                alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "
                    << tr.target.ptr << ": " << indent << reply << dedent << endl;
            }
            
        }
        break;
    
   ...

    if (result != NO_ERROR) {
        mLastError = result;
    }
    
    return result;
}

似曾相识的场景,但是我们重点关注下BR_TRANSACTION, 先回想下用C实现时在接收到BR_TRANSACTION消息时是什么流程?

①: 将读取的数据转换成binder_transaction_data类型;
②: 调用形参的函数指针;
③:发送回复数据;

那看看我们这里好像和原来描述的步骤一样,只是执行服务的方式变了,注意看着:

            if (tr.target.ptr) {
                sp<BBinder> b((BBinder*)tr.cookie);
                error = b->transact(tr.code, buffer, &reply, tr.flags);
            }

将cookie转换成了BBinder对象,这个值是我们写C时没有用到的,笔者在写4.3节的时候马后炮指的就是这了;
在注册服务的时候binder的服务已经被保存在这了,这里执行transact方法就相当于执行 BnXXXService::onTransact的方法,为什么这么说,详见BBinder的transact方法;

5.5 BBinder::transact

status_t BBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    data.setDataPosition(0);

    status_t err = NO_ERROR;
    switch (code) {
        case PING_TRANSACTION:
            reply->writeInt32(pingBinder());
            break;
        default:
            err = onTransact(code, data, reply, flags);
            break;
    }

    if (reply != NULL) {
        reply->setDataPosition(0);
    }

    return err;
}

onTransact该方法由具体的派生类复写;

六. 总结

BpxxxService主要用于封装了一些请求服务的方法供client使用,不需要像写C语言时,客户端只是获得handle,构造数据发送啥的都要自己去实现,现在只要获取到通过interface_cast就可以将对应的BpBinder转换成你想要的Service对象(详见3.1),然后就可以调用封装好的方法去请求服务;

BnxxxService主要封装了提供服务的方法,在收到BR_TRANSACTIONcmd后执行,主要继承于BBinder类;

PS:
踩个坑,在IServiceManager.cpp中有个BnServiceManager::onTransact方法,ServiceManager的服务是用c实现的,这个方法没有使用;

查看原文

赞 0 收藏 0 评论 0

Sourcelink 发布了文章 · 2018-12-01

Binder机制情景分析之transaction_stack

一. 概述

这里以注册服务为例,当led_control_service请求注册服务时是通过handle找到的ServiceManager,但是ServiceManager是如何找到led_control_service进行回复的呢?

答:这里驱动中用到了一个传送栈记录了发送和接收进程,线程的信息; 接下来我们讲下具体的流程;

二. transaction_stack的来源

主要代码在binder_transaction()binder_thread_read()中;

2.1 BC_TRANSACTION

led_control_service调用BC_TRANSACTION开始,此时驱动会调用binder_transaction()函数;

如下是该函数中的一段代码,这段代码前面在深入驱动时未讲解:

    if (!reply && !(tr->flags & TF_ONE_WAY))                  ①
        t->from = thread;
    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->priority = task_nice(current);
①: 判断是否需要回复,需要则记录下当前进程的thread信息;
②: 记录下要目标进程信息和线程信息;

这里tstruct binder_transaction 结构,和transaction_stack类型相同;

struct binder_transaction {
    int debug_id;
    struct binder_work work;
    struct binder_thread *from;                                 ①
    struct binder_transaction *from_parent;                     ②
    struct binder_proc *to_proc;                                ③
    struct binder_thread *to_thread;                            ④
    struct binder_transaction *to_parent;                       ⑤
    unsigned need_reply:1;
    .....
};
①: 记录发送线程信息;
②: 记录发送线程的传输栈的父栈;
③: 记录接收进程的进程信息;
④: 记录接收线程的进程信息;
⑤: 记录接收进程的传输栈的父栈;

此时t变量中的主要信息如下:

transaction_starck
fromled_control_service'thread
to_procServiceManager
to_threadServiceManager'thread

此时已经记录下了接收方的信息了,继续往下看(binder_transaction()函数内):

    if (reply) {
        BUG_ON(t->buffer->async_transaction != 0);
        binder_pop_transaction(target_thread, in_reply_to);
    } else if (!(t->flags & TF_ONE_WAY)) {                     ①
        BUG_ON(t->buffer->async_transaction != 0);
        t->need_reply = 1;
        t->from_parent = thread->transaction_stack;            ②
        thread->transaction_stack = t;                         ③
    } else {
        BUG_ON(target_node == NULL);
        BUG_ON(t->buffer->async_transaction != 1);
        if (target_node->has_async_transaction) {
            target_list = &target_node->async_todo;
            target_wait = NULL;
        } else
            target_node->has_async_transaction = 1;
    }
①: 判断是否需要回复;
②: 这里一个入栈操作将当前线程的传输压入,但是thread->transaction_stack此时为NULL,因为第一次执行前面没有赋值;
③: 接下给当前线程的传输栈赋值,;

此时led_control_service线程的传输栈信息如下:

transaction_starck
fromled_control_service'thread
to_procServiceManager
to_threadServiceManager'thread
from_parentNULL

2.2 BR_TRANSACTION

前面驱动讲解过,在led_contrl_server执行binder_transaction()后,ServiceManager的进程会被唤醒,则ServiceManager会从休眠中醒来继续执行binder_thread_read;

binder_thread_read有段代码如下:

    if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
        t->to_parent = thread->transaction_stack;                ①
        t->to_thread = thread;                                   ②
        thread->transaction_stack = t;                           ③
    } else {
        t->buffer->transaction = NULL;
        kfree(t);
        binder_stats_deleted(BINDER_STAT_TRANSACTION);
    }
①: 将当前线程传输栈入栈;
②: 记录接收进程的信息,也就是自己本身,因为他本身是被唤醒的;
③: 当前线程传输栈记录下线程信息;

这里的t是从带处理事务的链表中取出来的,也就是前面led_control_service挂到ServiceManager的todo链表上;

ServiceManager线程的传输栈的信息如下:

transaction_starck
fromled_control_service'thread
to_procServiceManager
to_threadServiceManager'thread
from_parentNULL
to_parentNULL

到这里线程传输栈的数据来源和构造讲完;

三. transaction_stack的使用

ServiceManager的用户态在处理完注册信息后,调用BC_REPLAY命令回复注册结果给led_control_service,此时驱动中也是调用到binder_transaction();

binder_transaction代码片如下:

if (reply) {
        in_reply_to = thread->transaction_stack;                   ①
        ALOGD("%s:%d,%d %s in_reply_to = thread->transaction_stack\n", proc->tsk->comm, proc->pid, thread->pid, __FUNCTION__);
        if (in_reply_to == NULL) {
            binder_user_error("%d:%d got reply transaction with no transaction stack\n",
                      proc->pid, thread->pid);
            return_error = BR_FAILED_REPLY;
            goto err_empty_call_stack;
        }
        binder_set_nice(in_reply_to->saved_priority);
        if (in_reply_to->to_thread != thread) {                    ②
            return_error = BR_FAILED_REPLY;
            in_reply_to = NULL;
            goto err_bad_call_stack;
        }
        thread->transaction_stack = in_reply_to->to_parent;        ③
        target_thread = in_reply_to->from;                         ④
        if (target_thread == NULL) {
            return_error = BR_DEAD_REPLY;
            goto err_dead_binder;
        }
        if (target_thread->transaction_stack != in_reply_to) {
            return_error = BR_FAILED_REPLY;
            in_reply_to = NULL;
            target_thread = NULL;
            goto err_dead_binder;
        }
        target_proc = target_thread->proc;
    } else {
①: 用个临时变量记录下当前线程额传输栈信息;
②: 判断下接收的线程是否为自己本身,如果不是则出错,看迷糊的可以看下再2.2节;
③: 一次出栈操作,此时thread->transaction_stack值为NULL了;
④: 获取到目标线程,from中记录着发送线程led_contol_service的信息;

这里就通过ServiceManager在read的时候入栈的传送栈信息,获取到发送进程的信息,即回复进程的信息;

接着往下看:

    if (reply) {
        BUG_ON(t->buffer->async_transaction != 0);
        binder_pop_transaction(target_thread, in_reply_to);    ①
    } else if (!(t->flags & TF_ONE_WAY)) {
        BUG_ON(t->buffer->async_transaction != 0);
        t->need_reply = 1;
        t->from_parent = thread->transaction_stack;
        thread->transaction_stack = t;
    } else {
      ...
    }
①: 一个出栈操作;

此时led_control_service的传输栈也指向了父栈,即为空且清除了in_reply_tofrom的信息;

上面那部ServiceManager进程已经知道此时要发送给谁,到此该问题就完美解答了;

查看原文

赞 0 收藏 0 评论 0

Sourcelink 发布了文章 · 2018-11-25

Binder机制情景分析之深入驱动

一. 概述

看过上篇C服务应用篇内容你肯定已经了解binder的一个使用过程,但是肯定还会有很多疑问:

    1. service注册服务是怎么和ServiceManager联系上的?
    1. client是怎么根据服务名找到的service进程?
    1. client获取的handle和service注册到ServiceManager的handle是否相同?
    1. client通过handle是怎么调用的服务?

这篇开始结合binder驱动进行数据交互的分析;

1.1 驱动中重要的数据结构

数据结构说明
binder_proc每个使用open打开binder设备文件的进程都会在驱动中创建一个binder_proc的结构, 用来记录该<bar>进程的各种信息和状态.例如:线程表,binder节点表,节点引用表
binder_thread每个binder线程在binder驱动中都有一个对应的binder_thread结构.记录了线程相关的信息,例如需要完成的任务等.
binder_nodebindder_proc 中有一张binder节点对象表,表项是binder_node结构.
binder_refbinder_proc还有一张节点引用表,表象是binder_ref结构. 保存所引用对象的binder_node指针.
binder_buffer驱动通过mmap的方式创建了一块大的缓存区,每次binder传输数据,会在缓存区分配一个binder_buffer的结构来保存数据.

1.2 说明

先讲解关于binder应用层调用binder_open()相关的公用流程的驱动代码;

二. binder初始化-公共

2.1 binder_open

应用层open了binder驱动,对应驱动层代码如下:

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;

    binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n",
             current->group_leader->pid, current->pid);

    proc = kzalloc(sizeof(*proc), GFP_KERNEL);                                        ①
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);                                                         ②
    proc->tsk = current;
    INIT_LIST_HEAD(&proc->todo);                                                      ③
    init_waitqueue_head(&proc->wait);
    proc->default_priority = task_nice(current);

    binder_lock(__func__);

    binder_stats_created(BINDER_STAT_PROC);
    hlist_add_head(&proc->proc_node, &binder_procs);                                  ④
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc;                                                        ⑤

    binder_unlock(__func__);

    if (binder_debugfs_dir_entry_proc) {
        char strbuf[11];

        snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
        proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO,
            binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);
    }

    return 0;
}
①: 为当前进程分配一个struct binder_proc宽度空间给proc;
②: 获取当前进程的task结构;
③: 初始化binder_proc中的todo链表;
④: 将当前进程的binder_proc插入全局变量binder_procs中;
⑤: 将proc保存到文件结构中,供下次调用使用;

binder_procs这是一个全局的红黑树变量,该全局变量在binder驱动的最前方使用 static HLIST_HEAD(binder_procs);进行的初始化;
binder_open()函数的主要功能是打开binder驱动的设备文件,为当前进程创建和初始化binder_proc结构体proc.将proc插入到全局的红黑树binder_procs中,供将来查找用;
同时变量proc还放到file结构的private_data字段中,调用驱动的其他操作时可从file结构中取出代表当前进程的binder_proc结构体使用;

2.2 binder_ioctl

应用层调用ioctl时对应的驱动层代码:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret;
    struct binder_proc *proc = filp->private_data;
    struct binder_thread *thread;
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;

    /*pr_info("binder_ioctl: %d:%d %x %lx\n",
            proc->pid, current->pid, cmd, arg);*/

    trace_binder_ioctl(cmd, arg);

    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
    if (ret)
        goto err_unlocked;

    binder_lock(__func__);
    thread = binder_get_thread(proc);                                                ①
    if (thread == NULL) {
        ret = -ENOMEM;
        goto err;
    }

    switch (cmd) {
    case BINDER_WRITE_READ:
        ret = binder_ioctl_write_read(filp, cmd, arg, thread);
        if (ret)
            goto err;
        break;
    case BINDER_SET_MAX_THREADS:
        if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
            ret = -EINVAL;
            goto err;
        }
        break;
    case BINDER_SET_CONTEXT_MGR:
        ret = binder_ioctl_set_ctx_mgr(filp);
        if (ret)
            goto err;
        break;
    case BINDER_THREAD_EXIT:
        binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
                 proc->pid, thread->pid);
        binder_free_thread(proc, thread);
        thread = NULL;
        break;
    case BINDER_VERSION: {
        struct binder_version __user *ver = ubuf;

        if (size != sizeof(struct binder_version)) {
            ret = -EINVAL;
            goto err;
        }
        if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
                 &ver->protocol_version)) {
            ret = -EINVAL;
            goto err;
        }
        break;
    }
    default:
        ret = -EINVAL;
        goto err;
    }
    ret = 0;
err:
    if (thread)
        thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
    binder_unlock(__func__);
    wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
    if (ret && ret != -ERESTARTSYS)
        pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
    trace_binder_ioctl_done(ret);
    return ret;
}

获取binder版本号很简单,发BINDER_VERSION命令给驱动,驱动回复BINDER_CURRENT_PROTOCOL_VERSION给用户空间;
这里讲下各个命令的意义:

命令含义数据格式
BINDER_WRITE_READ向驱动读取和写入数据.可同时读和写struct binder_write_read
BINDER_SET_MAX_THREADS设置线程池的最大的线程数,达到上限后驱动将不会在通知应用层启动新线程size_t
BINDER_SET_CONTEXT_MGR将本进程设置为binder系统的管理进程,只有servicemanager进程才会使用这个命令且只能调用一次int
BINDER_THREAD_EXIT通知驱动当前线程要退出了,以便驱动清理该线程相关的数据int
BINDER_VERSION获取binder的版本号struct binder_version

但是这有个注意点:

①: 第一次调用ioctl时会为该进程创建一个线程;

2.3 binder_get_thread

static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
    struct binder_thread *thread = NULL;
    struct rb_node *parent = NULL;
    struct rb_node **p = &proc->threads.rb_node;

    while (*p) {                                                                        ①
        parent = *p;
        thread = rb_entry(parent, struct binder_thread, rb_node);

        if (current->pid < thread->pid)
            p = &(*p)->rb_left;
        else if (current->pid > thread->pid)
            p = &(*p)->rb_right;
        else
            break;
    }
    if (*p == NULL) {
        thread = kzalloc(sizeof(*thread), GFP_KERNEL);                                    ②
        if (thread == NULL)
            return NULL;
        binder_stats_created(BINDER_STAT_THREAD);
        thread->proc = proc;
        thread->pid = current->pid;
        init_waitqueue_head(&thread->wait);                                               ③
        INIT_LIST_HEAD(&thread->todo);
        rb_link_node(&thread->rb_node, parent, p);
        rb_insert_color(&thread->rb_node, &proc->threads);                                ④
        thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
        thread->return_error = BR_OK;
        thread->return_error2 = BR_OK;
    }
    return thread;
}

该函数的主要功能是从当前进程信息表中找到挂在下面的线程,struct binder_thread是用挂在进程信息表下threads节点的红黑树链表下;

①: 先遍历threads节点的红黑树链表;
②: 如果没有查找到,则分配一个struct binder_thread长度的空间;
③: 初始化等待队列头节点和thread的todo链表;
④: 将该线程插入到进程的threads节点;

首先先遍历该链表,如果为找到线程信息则创建一个binder_thread线程,接着初始化线程等待队列和线程的todo链表,再将该线程节点挂在进程的信息表中的threads节点的红黑树中;

2.4 binder_mmap

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;                                   ①
    const char *failure_string;
    struct binder_buffer *buffer;

    if (proc->tsk != current)
        return -EINVAL;

    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;

    binder_debug(BINDER_DEBUG_OPEN_CLOSE,
             "binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
             proc->pid, vma->vm_start, vma->vm_end,
             (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
             (unsigned long)pgprot_val(vma->vm_page_prot));

    if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
        ret = -EPERM;
        failure_string = "bad vm_flags";
        goto err_bad_arg;
    }
    vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

    mutex_lock(&binder_mmap_lock);
    if (proc->buffer) {
        ret = -EBUSY;
        failure_string = "already mapped";
        goto err_already_mapped;
    }

    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);                     ②
    if (area == NULL) {
        ret = -ENOMEM;
        failure_string = "get_vm_area";
        goto err_get_vm_area_failed;
    }
    proc->buffer = area->addr;                                                       ③
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
    mutex_unlock(&binder_mmap_lock);

#ifdef CONFIG_CPU_CACHE_VIPT
    if (cache_is_vipt_aliasing()) {
        while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {
            pr_info("binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);
            vma->vm_start += PAGE_SIZE;
        }
    }
#endif
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL); ④
    if (proc->pages == NULL) {
        ret = -ENOMEM;
        failure_string = "alloc page array";
        goto err_alloc_pages_failed;
    }
    proc->buffer_size = vma->vm_end - vma->vm_start;

    vma->vm_ops = &binder_vm_ops;
    vma->vm_private_data = proc;

    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {    ⑤
        ret = -ENOMEM;
        failure_string = "alloc small buf";
        goto err_alloc_small_buf_failed;
    }
    buffer = proc->buffer;
    INIT_LIST_HEAD(&proc->buffers);
    list_add(&buffer->entry, &proc->buffers);                                                ⑥
    buffer->free = 1;
    binder_insert_free_buffer(proc, buffer);
    proc->free_async_space = proc->buffer_size / 2;
    barrier();
    proc->files = get_files_struct(current);
    proc->vma = vma;
    proc->vma_vm_mm = vma->vm_mm;

    /*pr_info("binder_mmap: %d %lx-%lx maps %p\n",
         proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
    return 0;

err_alloc_small_buf_failed:
    kfree(proc->pages);
    proc->pages = NULL;
err_alloc_pages_failed:
    mutex_lock(&binder_mmap_lock);
    vfree(proc->buffer);
    proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
    mutex_unlock(&binder_mmap_lock);
err_bad_arg:
    pr_err("binder_mmap: %d %lx-%lx %s failed %d\n",
           proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
    return ret;
}
①: filp->private_data保存了我们open设备时创建的binder_proc信息;
②: 为用户进程分配一块内核空间作为缓冲区;
③: 把分配的缓冲区指针存放到binder_proc的buffer字段;
④: 分配pages空间;
④: 在内核分配一块同样页数的内核空间,并把它的物理内存和前面为用户进程分配的内存地址关联;
⑤: 将刚才分配的内存块加入用户进程内存链表;

binder_mmap函数首先调用get_vm_area()分配一块地址空间,这里创建的为虚拟内存,位于用户进程空间,接着调用binder_update_page_range()建立虚拟内存到物理内存的映射,这样用户空间和内核空间就能共享一块空间了;

binder运用了mmap机制,在进程间的数据传输时就减小了拷贝次数; 如果不用mmap,从发送进程拷贝到内核空间调用一次copy_from_user,从内核空间到目标进程又需要调用copy_to_user,这样就发生两次数据拷贝.但运用了mmap后,只需要把发送的进程用户空间数据拷贝到发送进程的内核空间调用一次copy_from_user,因为目标进程内核空间缓存区和发送进程内核空间的缓冲区是共享;

三. ServiceManager

3.1 注册为Manager

3.1.1 binder_ioctl_set_ctx_mgr

static int binder_ioctl_set_ctx_mgr(struct file *filp)
{
    int ret = 0;
    struct binder_proc *proc = filp->private_data;
    kuid_t curr_euid = current_euid();

    if (binder_context_mgr_node != NULL) {
        pr_err("BINDER_SET_CONTEXT_MGR already set\n");
        ret = -EBUSY;
        goto out;
    }
    ret = security_binder_set_context_mgr(proc->tsk);
    if (ret < 0)
        goto out;
    if (uid_valid(binder_context_mgr_uid)) {
        if (!uid_eq(binder_context_mgr_uid, curr_euid)) {
            pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
                   from_kuid(&init_user_ns, curr_euid),
                   from_kuid(&init_user_ns,
                    binder_context_mgr_uid));
            ret = -EPERM;
            goto out;
        }
    } else {
        binder_context_mgr_uid = curr_euid;                                    ①
    }
    binder_context_mgr_node = binder_new_node(proc, 0, 0);                   ②
    if (binder_context_mgr_node == NULL) {
        ret = -ENOMEM;
        goto out;
    }
    binder_context_mgr_node->local_weak_refs++;
    binder_context_mgr_node->local_strong_refs++;
    binder_context_mgr_node->has_strong_ref = 1;
    binder_context_mgr_node->has_weak_ref = 1;
out:
    return ret;
}
①: 保存当前进程的用户id到全局变量binder_context_mgr_uid;
②: 为当前进程创建一个binder_node节点,保存到全局变量binder_context_mgr_node;

3.2 进入循环

调用ioctl函数写入BC_ENTER_LOOPER命令给驱动,进入循环;
当调用ioctl函数时命令为BINDER_WRITE_READ则调用下面函数:

3.2.1 binder_ioctl_write_read

static int binder_ioctl_write_read(struct file *filp,
                unsigned int cmd, unsigned long arg,
                struct binder_thread *thread)
{
    int ret = 0;
    struct binder_proc *proc = filp->private_data;
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;
    struct binder_write_read bwr;

    if (size != sizeof(struct binder_write_read)) {
        ret = -EINVAL;
        goto out;
    }
    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {                                    ①
        ret = -EFAULT;
        goto out;
    }
    binder_debug(BINDER_DEBUG_READ_WRITE,
             "%d:%d write %lld at %016llx, read %lld at %016llx\n",
             proc->pid, thread->pid,
             (u64)bwr.write_size, (u64)bwr.write_buffer,
             (u64)bwr.read_size, (u64)bwr.read_buffer);

    if (bwr.write_size > 0) {                                                         ②
        ret = binder_thread_write(proc, thread,
                      bwr.write_buffer,
                      bwr.write_size,
                      &bwr.write_consumed);
        trace_binder_write_done(ret);
        if (ret < 0) {
            bwr.read_consumed = 0;
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }
    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);
        trace_binder_read_done(ret);
        if (!list_empty(&proc->todo))
            wake_up_interruptible(&proc->wait);
        if (ret < 0) {
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }
    binder_debug(BINDER_DEBUG_READ_WRITE,
             "%d:%d wrote %lld of %lld, read return %lld of %lld\n",
             proc->pid, thread->pid,
             (u64)bwr.write_consumed, (u64)bwr.write_size,
             (u64)bwr.read_consumed, (u64)bwr.read_size);
    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
        ret = -EFAULT;
        goto out;
    }
out:
    return ret;
}
①: 将用户空间的数据拷贝到内核空间;
②: 这里可以看到驱动判断读写是根据读写buf的size来分辨且读写操作互不干扰;
struct binder_write_read {
    binder_size_t        write_size;    /* bytes to write */
    binder_size_t        write_consumed;    /* bytes consumed by driver */
    binder_uintptr_t    write_buffer;
    binder_size_t        read_size;    /* bytes to read */
    binder_size_t        read_consumed;    /* bytes consumed by driver */
    binder_uintptr_t    read_buffer;
};

这个结构体数据很简单,一般只要填写size和buf指针就可以,buf的数据个数C服务应用篇有介绍;

3.2.2 binder_thread_write

因为binder_thread_write()太长了所以每次用到了哪个命令再细讲;

3.2.2.1 BC_ENTER_LOOPER

        case BC_ENTER_LOOPER:
            binder_debug(BINDER_DEBUG_THREADS,
                     "%d:%d BC_ENTER_LOOPER\n",
                     proc->pid, thread->pid);
            if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {
                thread->looper |= BINDER_LOOPER_STATE_INVALID;
                binder_user_error("%d:%d ERROR: BC_ENTER_LOOPER called after BC_REGISTER_LOOPER\n",
                    proc->pid, thread->pid);
            }
            thread->looper |= BINDER_LOOPER_STATE_ENTERED;
            break;

这个命令有解释过告诉该线程进入循环状态,这个线程就是前面第一次调用ioctl创建的struct binder_thread结构的线程;

3.2.3 binder_thread_read

接下来进入for循环后,又调用了一次ioctl进行读操作

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)
{
    void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;

    int ret = 0;
    int wait_for_proc_work;

    if (*consumed == 0) {                                                        ①
        if (put_user(BR_NOOP, (uint32_t __user *)ptr))
            return -EFAULT;
        ptr += sizeof(uint32_t);
    }

retry:
    wait_for_proc_work = thread->transaction_stack == NULL &&                    ②
                list_empty(&thread->todo);

    if (thread->return_error != BR_OK && ptr < end) {
        if (thread->return_error2 != BR_OK) {
            if (put_user(thread->return_error2, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);
            binder_stat_br(proc, thread, thread->return_error2);
            if (ptr == end)
                goto done;
            thread->return_error2 = BR_OK;
        }
        if (put_user(thread->return_error, (uint32_t __user *)ptr))
            return -EFAULT;
        ptr += sizeof(uint32_t);
        binder_stat_br(proc, thread, thread->return_error);
        thread->return_error = BR_OK;
        goto done;
    }


    thread->looper |= BINDER_LOOPER_STATE_WAITING;
    if (wait_for_proc_work)                                                     ③
        proc->ready_threads++;

    binder_unlock(__func__);

    trace_binder_wait_for_work(wait_for_proc_work,
                   !!thread->transaction_stack,
                   !list_empty(&thread->todo));
    if (wait_for_proc_work) {
        if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |                  ④
                    BINDER_LOOPER_STATE_ENTERED))) {
            binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n",
                proc->pid, thread->pid, thread->looper);
            wait_event_interruptible(binder_user_error_wait,
                         binder_stop_on_user_error < 2);
        }
        binder_set_nice(proc->default_priority);
        if (non_block) {
            if (!binder_has_proc_work(proc, thread))
                ret = -EAGAIN;
        } else
            ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));    ⑤
    } else {
        if (non_block) {
            if (!binder_has_thread_work(thread))
                ret = -EAGAIN;
        } else
            ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread));
    }

    binder_lock(__func__);

    if (wait_for_proc_work)                                                 ⑥
        proc->ready_threads--;
    thread->looper &= ~BINDER_LOOPER_STATE_WAITING;

    if (ret)
        return ret;

    while (1) {
        uint32_t cmd;
        struct binder_transaction_data tr;
        struct binder_work *w;
        struct binder_transaction *t = NULL;

        if (!list_empty(&thread->todo)) {                                     ⑦
            w = list_first_entry(&thread->todo, struct binder_work,
                         entry);
        } else if (!list_empty(&proc->todo) && wait_for_proc_work) {          ⑧
            w = list_first_entry(&proc->todo, struct binder_work,
                         entry);
        } else {
            /* no data added */
            if (ptr - buffer == 4 &&
                !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN))
                goto retry;
            break;
        }

        if (end - ptr < sizeof(tr) + 4)
            break;

        switch (w->type) {
        case BINDER_WORK_TRANSACTION: {
            t = container_of(w, struct binder_transaction, work);              ⑨
        } break;
        case BINDER_WORK_TRANSACTION_COMPLETE: {
            cmd = BR_TRANSACTION_COMPLETE;
            if (put_user(cmd, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);

            binder_stat_br(proc, thread, cmd);
            binder_debug(BINDER_DEBUG_TRANSACTION_COMPLETE,
                     "%d:%d BR_TRANSACTION_COMPLETE\n",
                     proc->pid, thread->pid);

            list_del(&w->entry);                                               ⑩
            kfree(w);
            binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
        } break;
        case BINDER_WORK_NODE: {
            struct binder_node *node = container_of(w, struct binder_node, work);
            uint32_t cmd = BR_NOOP;
            const char *cmd_name;
            int strong = node->internal_strong_refs || node->local_strong_refs;
            int weak = !hlist_empty(&node->refs) || node->local_weak_refs || strong;

            if (weak && !node->has_weak_ref) {
                cmd = BR_INCREFS;
                cmd_name = "BR_INCREFS";
                node->has_weak_ref = 1;
                node->pending_weak_ref = 1;
                node->local_weak_refs++;
            } else if (strong && !node->has_strong_ref) {
                cmd = BR_ACQUIRE;
                cmd_name = "BR_ACQUIRE";
                node->has_strong_ref = 1;
                node->pending_strong_ref = 1;
                node->local_strong_refs++;
            } else if (!strong && node->has_strong_ref) {
                cmd = BR_RELEASE;
                cmd_name = "BR_RELEASE";
                node->has_strong_ref = 0;
            } else if (!weak && node->has_weak_ref) {
                cmd = BR_DECREFS;
                cmd_name = "BR_DECREFS";
                node->has_weak_ref = 0;
            }
            if (cmd != BR_NOOP) {
                if (put_user(cmd, (uint32_t __user *)ptr))
                    return -EFAULT;
                ptr += sizeof(uint32_t);
                if (put_user(node->ptr,
                         (binder_uintptr_t __user *)ptr))
                    return -EFAULT;
                ptr += sizeof(binder_uintptr_t);
                if (put_user(node->cookie,
                         (binder_uintptr_t __user *)ptr))
                    return -EFAULT;
                ptr += sizeof(binder_uintptr_t);

                binder_stat_br(proc, thread, cmd);
                binder_debug(BINDER_DEBUG_USER_REFS,
                         "%d:%d %s %d u%016llx c%016llx\n",
                         proc->pid, thread->pid, cmd_name,
                         node->debug_id,
                         (u64)node->ptr, (u64)node->cookie);
            } else {
                list_del_init(&w->entry);
                if (!weak && !strong) {
                    binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                             "%d:%d node %d u%016llx c%016llx deleted\n",
                             proc->pid, thread->pid,
                             node->debug_id,
                             (u64)node->ptr,
                             (u64)node->cookie);
                    rb_erase(&node->rb_node, &proc->nodes);
                    kfree(node);
                    binder_stats_deleted(BINDER_STAT_NODE);
                } else {
                    binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                             "%d:%d node %d u%016llx c%016llx state unchanged\n",
                             proc->pid, thread->pid,
                             node->debug_id,
                             (u64)node->ptr,
                             (u64)node->cookie);
                }
            }
        } break;
        case BINDER_WORK_DEAD_BINDER:
        case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
        case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
            struct binder_ref_death *death;
            uint32_t cmd;

            death = container_of(w, struct binder_ref_death, work);
            if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
                cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;
            else
                cmd = BR_DEAD_BINDER;
            if (put_user(cmd, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);
            if (put_user(death->cookie,
                     (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);
            binder_stat_br(proc, thread, cmd);
            binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION,
                     "%d:%d %s %016llx\n",
                      proc->pid, thread->pid,
                      cmd == BR_DEAD_BINDER ?
                      "BR_DEAD_BINDER" :
                      "BR_CLEAR_DEATH_NOTIFICATION_DONE",
                      (u64)death->cookie);

            if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) {
                list_del(&w->entry);
                kfree(death);
                binder_stats_deleted(BINDER_STAT_DEATH);
            } else
                list_move(&w->entry, &proc->delivered_death);
            if (cmd == BR_DEAD_BINDER)
                goto done; /* DEAD_BINDER notifications can cause transactions */
        } break;
        }

        if (!t)
            continue;

        BUG_ON(t->buffer == NULL);
        if (t->buffer->target_node) {
            struct binder_node *target_node = t->buffer->target_node;

            tr.target.ptr = target_node->ptr;
            tr.cookie =  target_node->cookie;
            t->saved_priority = task_nice(current);
            if (t->priority < target_node->min_priority &&
                !(t->flags & TF_ONE_WAY))
                binder_set_nice(t->priority);
            else if (!(t->flags & TF_ONE_WAY) ||
                 t->saved_priority > target_node->min_priority)
                binder_set_nice(target_node->min_priority);
            cmd = BR_TRANSACTION;                                              ①①
        } else {
            tr.target.ptr = 0;
            tr.cookie = 0;
            cmd = BR_REPLY;
        }
        tr.code = t->code;                                                   ①②
        tr.flags = t->flags;
        tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);

        if (t->from) {
            struct task_struct *sender = t->from->proc->tsk;

            tr.sender_pid = task_tgid_nr_ns(sender,
                            task_active_pid_ns(current));
        } else {
            tr.sender_pid = 0;
        }

        tr.data_size = t->buffer->data_size;
        tr.offsets_size = t->buffer->offsets_size;
        tr.data.ptr.buffer = (binder_uintptr_t)(
                    (uintptr_t)t->buffer->data +
                    proc->user_buffer_offset);
        tr.data.ptr.offsets = tr.data.ptr.buffer +
                    ALIGN(t->buffer->data_size,
                        sizeof(void *));

        if (put_user(cmd, (uint32_t __user *)ptr))
            return -EFAULT;
        ptr += sizeof(uint32_t);
        if (copy_to_user(ptr, &tr, sizeof(tr)))                               ①③
            return -EFAULT;
        ptr += sizeof(tr);

        trace_binder_transaction_received(t);
        binder_stat_br(proc, thread, cmd);
        binder_debug(BINDER_DEBUG_TRANSACTION,
                 "%d:%d %s %d %d:%d, cmd %d size %zd-%zd ptr %016llx-%016llx\n",
                 proc->pid, thread->pid,
                 (cmd == BR_TRANSACTION) ? "BR_TRANSACTION" :
                 "BR_REPLY",
                 t->debug_id, t->from ? t->from->proc->pid : 0,
                 t->from ? t->from->pid : 0, cmd,
                 t->buffer->data_size, t->buffer->offsets_size,
                 (u64)tr.data.ptr.buffer, (u64)tr.data.ptr.offsets);

        list_del(&t->work.entry);
        t->buffer->allow_user_free = 1;
        if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
            t->to_parent = thread->transaction_stack;
            t->to_thread = thread;
            thread->transaction_stack = t;
        } else {
            t->buffer->transaction = NULL;
            kfree(t);
            binder_stats_deleted(BINDER_STAT_TRANSACTION);
        }
        break;
    }

done:

    *consumed = ptr - buffer;                                             ①④
    if (proc->requested_threads + proc->ready_threads == 0 &&
        proc->requested_threads_started < proc->max_threads &&
        (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
         BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
         /*spawn a new thread if we leave this out */) {
        proc->requested_threads++;
        binder_debug(BINDER_DEBUG_THREADS,
                 "%d:%d BR_SPAWN_LOOPER\n",
                 proc->pid, thread->pid);
        if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
            return -EFAULT;
        binder_stat_br(proc, thread, BR_SPAWN_LOOPER);
    }
    return 0;
}
①: 先判断readbuf结构传进来的值是否为0,为0则返回一个BR_NOOP到用户空间,但是此时用户空间线程是睡眠的;
②: 如果当前线程的todo链表为空且传送数据栈无数据时,则表示当前进程空闲;
③: 如果当前进程在writeBC_REGISTER_LOOPER or BC_ENTER_LOOPER前就开始执行读操作,则进入休眠;
④: 进入休眠,唤醒后会检测binder_has_proc_work,当前进程是否工作(判断当前进程的todo链表是否为空和thread的looper状态),未工作则继续休眠;
⑤: 进入休眠,唤醒后会检测当前线程是否工作;
⑥: 能走到这说明已经唤醒了,进程的ready线程计数减一,且线程的looper等待状态清除;
⑦: 先查看线程的todo链表中是否有要执行的工作;
⑧: 再检查进程的todo链表中是否有需要执行的工作;
⑨: 通过binder_work节点找到struct binder_transaction结构体地址;
⑩:如果工作类型是TRANSACTION_COMPLETE则表示工作已经执行完了,可以将此工作从线程或进程的todo链表中删除;
①①: 回复命令BR_TRANSACTIONBR_REPLY;
①②: 填充回复的数据;
①③: 将tr的数据拷贝到用户空间,ptr指针指向的是用户空间的那个readbuf;
①④: 最后consumed中保存了此次回复数据的长度;

注意看第十三点:

        tr.data.ptr.buffer = (binder_uintptr_t)(
                    (uintptr_t)t->buffer->data +
                    proc->user_buffer_offset);

拷贝个体用户空间的仅仅是数据buf地址,因为使用了mmap,用户空间可以直接使用这块内存,这里也体现了拷贝一次的效率;

这里你可以发现read到的数据格式一般都是BR_NOOP+CMD+数据+CMD+数据....;
ServiceManager在刚进入循环开始第一次读操作时,没有其他线程就绪,此时只是返回一个BR_NOOP就开始休眠了;

3.3 总结流程

clipboard.png

四. led_control_service

4.1 注册服务

调用led_control_server.c中:

svcmgr_publish(bs, svcmgr, LED_CONTROL_SERVER_NAME, led_control);

注册为一个服务者,这里面主要调用了binder_call()写入了BC_TRANSACTION命令,详细的流程C服务应用篇已经写过了,现在就主要结合驱动分析;
这里需要注意的是binder_call()调用是同时读写binder驱动的,先看下写操作再看读操作;

4.1.1 binder_thread_write

4.1.1.1 BC_TRANSACTION

        case BC_TRANSACTION:
        case BC_REPLY: {
            struct binder_transaction_data tr;

            if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
            ptr += sizeof(tr);
            binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
            break;
        }

先将用户空间write_buffer数据拷贝到内核的struct binder_transaction_data tr中, 再调用binder_transaction()处理这些数据;

4.1.1.2 binder_transaction

这个函数巨长....

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
    struct binder_transaction *t;
    struct binder_work *tcomplete;
    binder_size_t *offp, *off_end;
    struct binder_proc *target_proc;
    struct binder_thread *target_thread = NULL;
    struct binder_node *target_node = NULL;
    struct list_head *target_list;
    wait_queue_head_t *target_wait;
    struct binder_transaction *in_reply_to = NULL;
    struct binder_transaction_log_entry *e;
    uint32_t return_error;

    e = binder_transaction_log_add(&binder_transaction_log);
    e->call_type = reply ? 2 : !!(tr->flags & TF_ONE_WAY);
    e->from_proc = proc->pid;
    e->from_thread = thread->pid;
    e->target_handle = tr->target.handle;
    e->data_size = tr->data_size;
    e->offsets_size = tr->offsets_size;

    if (reply) {
    ......
    } else {
        if (tr->target.handle) {
            struct binder_ref *ref;

            ref = binder_get_ref(proc, tr->target.handle);                                ①
            if (ref == NULL) {
                binder_user_error("%d:%d got transaction to invalid handle\n",
                    proc->pid, thread->pid);
                return_error = BR_FAILED_REPLY;
                goto err_invalid_target_handle;
            }
            target_node = ref->node;
        } else {
            target_node = binder_context_mgr_node;                                        ②
            if (target_node == NULL) {
                return_error = BR_DEAD_REPLY;
                goto err_no_context_mgr_node;
            }
        }
        e->to_node = target_node->debug_id;
        target_proc = target_node->proc;                                                ③
        if (target_proc == NULL) {
            return_error = BR_DEAD_REPLY;
            goto err_dead_binder;
        }
        if (security_binder_transaction(proc->tsk,
                        target_proc->tsk) < 0) {
            return_error = BR_FAILED_REPLY;
            goto err_invalid_target_handle;
        }
        if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {                   ④
            struct binder_transaction *tmp;

            tmp = thread->transaction_stack;
            if (tmp->to_thread != thread) {
                binder_user_error("%d:%d got new transaction with bad transaction stack, transaction %d has target %d:%d\n",
                    proc->pid, thread->pid, tmp->debug_id,
                    tmp->to_proc ? tmp->to_proc->pid : 0,
                    tmp->to_thread ?
                    tmp->to_thread->pid : 0);
                return_error = BR_FAILED_REPLY;
                goto err_bad_call_stack;
            }
            while (tmp) {
                if (tmp->from && tmp->from->proc == target_proc)                             ⑤
                    target_thread = tmp->from;
                tmp = tmp->from_parent;
            }
        }
    }
    if (target_thread) {
        e->to_thread = target_thread->pid;
        target_list = &target_thread->todo;
        target_wait = &target_thread->wait;
    } else {
        target_list = &target_proc->todo;
        target_wait = &target_proc->wait;
    }
    e->to_proc = target_proc->pid;

    /* TODO: reuse incoming transaction for reply */
    t = kzalloc(sizeof(*t), GFP_KERNEL);
    if (t == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_alloc_t_failed;
    }
    binder_stats_created(BINDER_STAT_TRANSACTION);

    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
    if (tcomplete == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_alloc_tcomplete_failed;
    }
    binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE);

    t->debug_id = ++binder_last_id;
    e->debug_id = t->debug_id;

    if (reply)
        binder_debug(BINDER_DEBUG_TRANSACTION,
                 "%d:%d BC_REPLY %d -> %d:%d, data %016llx-%016llx size %lld-%lld\n",
                 proc->pid, thread->pid, t->debug_id,
                 target_proc->pid, target_thread->pid,
                 (u64)tr->data.ptr.buffer,
                 (u64)tr->data.ptr.offsets,
                 (u64)tr->data_size, (u64)tr->offsets_size);
    else
        binder_debug(BINDER_DEBUG_TRANSACTION,
                 "%d:%d BC_TRANSACTION %d -> %d - node %d, data %016llx-%016llx size %lld-%lld\n",
                 proc->pid, thread->pid, t->debug_id,
                 target_proc->pid, target_node->debug_id,
                 (u64)tr->data.ptr.buffer,
                 (u64)tr->data.ptr.offsets,
                 (u64)tr->data_size, (u64)tr->offsets_size);

    if (!reply && !(tr->flags & TF_ONE_WAY))
        t->from = thread;
    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->priority = task_nice(current);

    trace_binder_transaction(reply, t, target_node);

    t->buffer = binder_alloc_buf(target_proc, tr->data_size,                       ⑥
        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
    if (t->buffer == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_binder_alloc_buf_failed;
    }
    t->buffer->allow_user_free = 0;
    t->buffer->debug_id = t->debug_id;
    t->buffer->transaction = t;
    t->buffer->target_node = target_node;
    trace_binder_transaction_alloc_buf(t->buffer);
    if (target_node)
        binder_inc_node(target_node, 1, 0, NULL);

    offp = (binder_size_t *)(t->buffer->data +
                 ALIGN(tr->data_size, sizeof(void *)));

    if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
               tr->data.ptr.buffer, tr->data_size)) {
        binder_user_error("%d:%d got transaction with invalid data ptr\n",
                proc->pid, thread->pid);
        return_error = BR_FAILED_REPLY;
        goto err_copy_data_failed;
    }

    if (copy_from_user(offp, (const void __user *)(uintptr_t)
               tr->data.ptr.offsets, tr->offsets_size)) {
        binder_user_error("%d:%d got transaction with invalid offsets ptr\n",
                proc->pid, thread->pid);
        return_error = BR_FAILED_REPLY;
        goto err_copy_data_failed;
    }
    if (!IS_ALIGNED(tr->offsets_size, sizeof(binder_size_t))) {
        binder_user_error("%d:%d got transaction with invalid offsets size, %lld\n",
                proc->pid, thread->pid, (u64)tr->offsets_size);
        return_error = BR_FAILED_REPLY;
        goto err_bad_offset;
    }
    off_end = (void *)offp + tr->offsets_size;
    for (; offp < off_end; offp++) {
        struct flat_binder_object *fp;

        if (*offp > t->buffer->data_size - sizeof(*fp) ||
            t->buffer->data_size < sizeof(*fp) ||
            !IS_ALIGNED(*offp, sizeof(u32))) {
            binder_user_error("%d:%d got transaction with invalid offset, %lld\n",
                      proc->pid, thread->pid, (u64)*offp);
            return_error = BR_FAILED_REPLY;
            goto err_bad_offset;
        }
        fp = (struct flat_binder_object *)(t->buffer->data + *offp);                   ⑦
        switch (fp->type) {
        case BINDER_TYPE_BINDER:
        case BINDER_TYPE_WEAK_BINDER: {
            struct binder_ref *ref;
            struct binder_node *node = binder_get_node(proc, fp->binder);                 ⑧

            if (node == NULL) {
                node = binder_new_node(proc, fp->binder, fp->cookie);
                if (node == NULL) {
                    return_error = BR_FAILED_REPLY;
                    goto err_binder_new_node_failed;
                }
                node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
                node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
            }
            if (fp->cookie != node->cookie) {
                binder_user_error("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n",
                    proc->pid, thread->pid,
                    (u64)fp->binder, node->debug_id,
                    (u64)fp->cookie, (u64)node->cookie);
                return_error = BR_FAILED_REPLY;
                goto err_binder_get_ref_for_node_failed;
            }
            if (security_binder_transfer_binder(proc->tsk,
                                target_proc->tsk)) {
                return_error = BR_FAILED_REPLY;
                goto err_binder_get_ref_for_node_failed;
            }
            ref = binder_get_ref_for_node(target_proc, node);                            ⑨
            if (ref == NULL) {
                return_error = BR_FAILED_REPLY;
                goto err_binder_get_ref_for_node_failed;
            }
            if (fp->type == BINDER_TYPE_BINDER)                                          ⑩
                fp->type = BINDER_TYPE_HANDLE;
            else
                fp->type = BINDER_TYPE_WEAK_HANDLE;
            fp->handle = ref->desc;                                                      ①①
            binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE,
                       &thread->todo);

            trace_binder_transaction_node_to_ref(t, node, ref);
            binder_debug(BINDER_DEBUG_TRANSACTION,
                     "        node %d u%016llx -> ref %d desc %d\n",
                     node->debug_id, (u64)node->ptr,
                     ref->debug_id, ref->desc);
        } break;

    .........

if (reply) {
        BUG_ON(t->buffer->async_transaction != 0);
        binder_pop_transaction(target_thread, in_reply_to);
    } else if (!(t->flags & TF_ONE_WAY)) {
        BUG_ON(t->buffer->async_transaction != 0);
        t->need_reply = 1;
        t->from_parent = thread->transaction_stack;
        thread->transaction_stack = t;
    } else {
        BUG_ON(target_node == NULL);
        BUG_ON(t->buffer->async_transaction != 1);
        if (target_node->has_async_transaction) {
            target_list = &target_node->async_todo;
            target_wait = NULL;
        } else
            target_node->has_async_transaction = 1;
    }
    t->work.type = BINDER_WORK_TRANSACTION;
    list_add_tail(&t->work.entry, target_list);                                     ①②
    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
    list_add_tail(&tcomplete->entry, &thread->todo);
    if (target_wait)
        wake_up_interruptible(target_wait);
    return;

.....

}
①: 目标handle不为0时说明是客户端调用服务端的情况;
②: 目标handle为0,说明是请求ServiceManager服务,保存目标节点struct binder_node;
③: 根据binder_node获取到binder_proc;
④: 判断此次调用是否需要reply;
⑤: 根据transaction_stack找到目标线程(第一次传输不会进来);
⑥: 目标进程的在mmap空间分配一块buf,接着调用copy_from_use将用户空间数据拷贝进刚分配的buf中,这样目标进程可以直接读取数据;
⑦: 获取struct flat_binder_object的首地址, offp保存的是object距数据头的偏移值,详细可以看下C服务应用的3.2节;
⑧: 为新传进来的binder实体构造一个binder_node;
⑨: 查看目标进程的refs_by_node红黑树上是否有ref指向该节点,如果没有则为该目标进程创建一个ref指向该node;
⑩: 原来的类型是binder实体,现在要传给ServiceManager就需要改变为handle引用类型;
①①: 把刚才创建的ref中的des赋值给等下要串给应用层的数据, 接着给该节点增加一次引用标记且会将一些事务加入自身线程的todo链表;
①②: 将需要处理的事务加入目标进程或目标线程的todo链表,并唤醒它;

现在ServiceManager进程的binder_proc中的refs_by_node红黑树上挂有一个新的binder_ref指向了传进来的binder实体;
且这个新挂上去的binder_refdesc成员为1(即传给应用层的handle),因为这是第一个指向该节点的引用,以后会递增;

4.1.2 binder_thread_read

  • 第一次read:

在写操作完后就开始读操作了,因为刚开始进程和线程的todo链表中没有需要处理的事务,再回复了BR_NOOP后就开始睡眠了;

写操作的时候有为创建的binder实体的node增加引用并加入了todo链表,这时led_control_service进程被唤醒;
开始处理BINDER_WORK_NODE事务,命令为BR_INCREFS, BR_ACQUIRE等;

  • 第二次read:

这次read是在处理完BR_INCREFS, BR_ACQUIRE等命令以后,又一次读数据,并进入睡眠;

PS: 这可以先不看,继续往下看ServiceManager被唤醒的流程;

好了,回到led_control_service进程了,被ServiceManager唤醒了;
这也没做啥事,就是构造数据后将其传回了用户空间;
接着发送释放buf的命令给内核空间,让它释放了内核mmap分配的数据;

4.1.3 ServiceManager被唤醒

4.1.3.1 binder_thread_read

该函数解析详细看3.2.3,这里说下ServiceManager进程被唤醒后做了哪些事情,

①: 先取出进程todo链表中需要处理的事务;
②: 再找到处理事务的struct binder_transaction结构体地址,取出刚才从发送进程拷贝进mmap分配的空间中数据进行处理;
③: 发送给ServiceManager的用户空间;
接着用户空间就会开始处理数据,数据格式如下:

用户空间在在收到数据后解析到BR_TRANSACTION命令后做了的流程分析见C服务应用篇2.4节;

4.1.3.2 binder_thread_write

    1. 应用层在调用do_add_serivice时最后还向驱动写入了两个命令(BC_ACQUIREBC_REQUEST_DEATH_NOTIFICATION);
    1. 执行完fun后写入reply,又写入两个命令(BC_FREE_BUFFERBC_REPLY);

4.1.3.2.1 BC_ACQUIRE


        switch (cmd) {
        case BC_INCREFS:
        case BC_ACQUIRE:
        case BC_RELEASE:
        case BC_DECREFS: {
            uint32_t target;
            struct binder_ref *ref;
            const char *debug_string;

            if (get_user(target, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);
            if (target == 0 && binder_context_mgr_node &&                                    ①
                (cmd == BC_INCREFS || cmd == BC_ACQUIRE)) {
                ref = binder_get_ref_for_node(proc,
                           binder_context_mgr_node);
                if (ref->desc != target) {
                    binder_user_error("%d:%d tried to acquire reference to desc 0, got %d instead\n",
                        proc->pid, thread->pid,
                        ref->desc);
                }
            } else
                ref = binder_get_ref(proc, target);
            if (ref == NULL) {
                binder_user_error("%d:%d refcount change on invalid ref %d\n",
                    proc->pid, thread->pid, target);
                break;
            }
            switch (cmd) {
            case BC_INCREFS:
                debug_string = "IncRefs";
                binder_inc_ref(ref, 0, NULL);
                break;
            case BC_ACQUIRE:                                                                ②
                debug_string = "Acquire";
                binder_inc_ref(ref, 1, NULL);
                break;
            case BC_RELEASE:
                debug_string = "Release";
                binder_dec_ref(ref, 1);
                break;
            case BC_DECREFS:
            default:
                debug_string = "DecRefs";
                binder_dec_ref(ref, 0);
                break;
            }
            binder_debug(BINDER_DEBUG_USER_REFS,
                     "%d:%d %s ref %d desc %d s %d w %d for node %d\n",
                     proc->pid, thread->pid, debug_string, ref->debug_id,
                     ref->desc, ref->strong, ref->weak, ref->node->debug_id);
            break;
        }
①: 这个是根据传进来的handle获取binder_ref;
②: 对刚才获取到ref增加强引用;

4.1.3.2.2 BC_REQUEST_DEATH_NOTIFICATION

case BC_REQUEST_DEATH_NOTIFICATION:
        case BC_CLEAR_DEATH_NOTIFICATION: {
            uint32_t target;
            binder_uintptr_t cookie;
            struct binder_ref *ref;
            struct binder_ref_death *death;

            if (get_user(target, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);
            if (get_user(cookie, (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);
            ref = binder_get_ref(proc, target);                                    ①
            if (ref == NULL) {
                break;
            }

            if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {
                if (ref->death) {
                    break;
                }
                death = kzalloc(sizeof(*death), GFP_KERNEL);
                if (death == NULL) {
                    thread->return_error = BR_ERROR;
                    break;
                }
                binder_stats_created(BINDER_STAT_DEATH);
                INIT_LIST_HEAD(&death->work.entry);
                death->cookie = cookie;
                ref->death = death;                                                  ②
                if (ref->node->proc == NULL) {
                    ref->death->work.type = BINDER_WORK_DEAD_BINDER;
                    if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
                        list_add_tail(&ref->death->work.entry, &thread->todo);
                    } else {
                        list_add_tail(&ref->death->work.entry, &proc->todo);
                        wake_up_interruptible(&proc->wait);
                    }
                }
            } else {
                if (ref->death == NULL) {
                    break;
                }
                death = ref->death;
                if (death->cookie != cookie) {
                    break;
                }
                ref->death = NULL;
                if (list_empty(&death->work.entry)) {
                    death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
                    if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
                        list_add_tail(&death->work.entry, &thread->todo);
                    } else {
                        list_add_tail(&death->work.entry, &proc->todo);
                        wake_up_interruptible(&proc->wait);
                    }
                } else {
                    BUG_ON(death->work.type != BINDER_WORK_DEAD_BINDER);
                    death->work.type = BINDER_WORK_DEAD_BINDER_AND_CLEAR;
                }
            }
        } break;
①: 根据handle获取ref;
②: 讲死亡通知挂到ref的death节点上;

这样操作后在这个ref指向的node节点的进程(这个场景为led_control_service),在死亡时会反馈给ServiceManager;

4.1.3.2.3 BC_FREE_BUFFER

        case BC_FREE_BUFFER: {
            binder_uintptr_t data_ptr;
            struct binder_buffer *buffer;

            if (get_user(data_ptr, (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);

            buffer = binder_buffer_lookup(proc, data_ptr);                            ①
            if (buffer == NULL) {
                break;
            }
            if (!buffer->allow_user_free) {
                break;
            }

            if (buffer->transaction) {
                buffer->transaction->buffer = NULL;
                buffer->transaction = NULL;
            }
            if (buffer->async_transaction && buffer->target_node) {
                BUG_ON(!buffer->target_node->has_async_transaction);
                if (list_empty(&buffer->target_node->async_todo))
                    buffer->target_node->has_async_transaction = 0;
                else
                    list_move_tail(buffer->target_node->async_todo.next, &thread->todo);
            }
            trace_binder_transaction_buffer_release(buffer);
            binder_transaction_buffer_release(proc, buffer, NULL);                   ②
            binder_free_buf(proc, buffer);
            break;
        }
①: 根据data.ptr.buffer的地址找到前面为拷贝led_control_service写入内核的数据而分配的mmap缓存区地址(详见4.1.1.2);
②: 释放那块buf;

这里需要注意data_ptr虽然是用户空间传来的,但是这也是由内核空间拷贝给用户空间的且该值在用户空间未改变;

4.1.3.3.4 BC_REPLY

        case BC_TRANSACTION:
        case BC_REPLY: {
            struct binder_transaction_data tr;

            if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
            ptr += sizeof(tr);
            binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
            break;
        }

这个流程在前面将BC_TRANSACTION为讲解,这里我们单独讲解下;

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{

    .......

    if (reply) {
        in_reply_to = thread->transaction_stack;
        if (in_reply_to == NULL) {
            binder_user_error("%d:%d got reply transaction with no transaction stack\n",
                      proc->pid, thread->pid);
            return_error = BR_FAILED_REPLY;
            goto err_empty_call_stack;
        }
        binder_set_nice(in_reply_to->saved_priority);
        if (in_reply_to->to_thread != thread) {
            return_error = BR_FAILED_REPLY;
            in_reply_to = NULL;
            goto err_bad_call_stack;
        }
        thread->transaction_stack = in_reply_to->to_parent;                    ①
        target_thread = in_reply_to->from;
        if (target_thread == NULL) {
            return_error = BR_DEAD_REPLY;
            goto err_dead_binder;
        }
        if (target_thread->transaction_stack != in_reply_to) {
            return_error = BR_FAILED_REPLY;
            in_reply_to = NULL;
            target_thread = NULL;
            goto err_dead_binder;
        }
        target_proc = target_thread->proc;                                     ②
    } else {
       ......
    }

    .....

  if (target_thread) {                                                    ③
        e->to_thread = target_thread->pid;
        target_list = &target_thread->todo;
        target_wait = &target_thread->wait;
    } else {
        target_list = &target_proc->todo;
        target_wait = &target_proc->wait;
    }
    ......
①: 从线程的传输栈上找到目标线程(当前进程为ServiceManager进程);
②: 通过目标线程查找到目标线程;
③: 这里可以看出reply是用线程的来完成的,因为是将要处理事情的是线程的todo链表;

接下来从用户空间拷贝数据,然后在将事务挂到目标线程的todo链表,再唤醒目标线程;
这样又回到了led_control_service进程了,请看4.1.2;

4.2 设置线程上限

    case BINDER_SET_MAX_THREADS:
        if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
            ret = -EINVAL;
            goto err;
        }
        break;

将上限值拷贝到proc的max_threads成员中保存;

4.3 总结流程

从注册服务开始说起:

clipboard.png

五. test_client

5.1 获取服务

client获取服务和service注册服务的流程几乎一样,流程中涉及到的驱动相关代码,上一篇都有讲解,这里放一个具体流程;
具体流程如下图:

clipboard.png

5.2 调用服务

调用服务的时候,是根据刚才获取的handle去调用和client获取服务的流程相同,只是目标进程变成了led_control_service,这里不再重复讲解了,请参考上图;

六. mmap用点

以下进程都是在内核态的描述;

6.1 内核态

在查看驱动源码时,发现注册服务时led_control_service进程将用户空间数据拷贝到内核后,再唤醒ServiceManager进程后,ServiceManager进程内核空间可以直接使用;

6.2 用户态

还有一点,在ServiceManager将内核空间数据拷贝到用户空间时,仅仅只是把刚才在led_control_service进程分配的mmap空间的地址传给了 ServiceManager的用户空间,而用户空间可以通过该地址直接访问数据了;

以上为mmap在binder的两点用法,跨进程,内核;

PS: 篇幅太长了,binder的知识点很多,还有好多需要更新;
流程从上往下看哈,顺序执行;

查看原文

赞 3 收藏 2 评论 0

Sourcelink 回答了问题 · 2018-11-23

解决怎么理解C语言中数据对象这一概念?

你好 根据你描述的补充,数据对象就相当用于存储数据的对象。然后数据对象类型有int char等等,然而C语言中有声明和定义两种东东,但是只有定义才会分配内存,也就是你描述的存储区域。
个人理解,这里描述的对象和变量应该是一个意思。

关注 4 回答 3

Sourcelink 关注了标签 · 2018-11-20

行业

行業,指主要根據職業、性質或具體事物,對社會各個領域稱呼。“行業”屬於中文表述的一種習慣,极少作为術語来表示特定的概念,有時與辭彙“产业”混淆使用,來表示同一概念,常指社会领域。行业属于概念广泛的常用中文词汇,其分类具有随意性。

关注 52437

Sourcelink 关注了标签 · 2018-11-20

segmentfault

SegmentFault (www.sf.gg) 是一个面向中文开发者的专业技术社区。社区采用良性、合理的机制来让开发者自由生长,希望通过最干净、简洁、优质的产品体验,来吸引国内优秀的开发者和技术人员,一起打造一个纯粹的技术交流社区。

我们希望为中文开发者提供一个纯粹、高质的技术交流平台,与开发者一起学习、交流与成长,创造属于开发者的时代!

网站产品

问答平台 专注高效地解决技术问题。确保内容质量的投票机制,合理区分的答案与回馈信息,用户参与改进的维基化内容,SegmentFault 帮你快捷地找到答案。

文章平台 简洁安静的技术经验分享。简约干净的界面,让你专注于内容的撰写;好用到爆的 Markdown 编辑器,和你的思维速度匹配无间。让你重新爱上写博客。

活动平台 在活动中找到志同道合的好基友。黑客马拉松、开发竞赛、线下沙龙、知识讲座、线上活动…… 总有一款适合你。

技术笔记 一个方便快捷的代码笔记本,使用 CodeMirror 编辑器,支持纯文本、Markdown、Java、CSS 等多种类型的文本渲染,还有神奇的笔记传送门。

程序员招聘平台 我们针对企业推出了面向开发者的专属招聘功能,企业组织可以展示自己的资料、团队成员、技术背景等内容。最重要的是,还能招募到契合的团队成员。

获得成就

  1. 3人创始团队创业初期利用1年时间独立开发底层框架,上线问答、博客、活动等社区平台,聚集十多万开发者。
  2. SegmentFault 团队将黑客马拉松活动引入中国,至今,已经在国内一线互联网城市以及台北、新加坡、硅谷等地区举办了超过 20 场黑客马拉松。SegmentFault 是目前中国最大的黑客马拉松组织方。
  3. SegmentFault 在 2013 中国新媒体创业大赛中获得全国决赛第二名,并入选微软创投加速器第 4 期,,并获得 IDG 资本数百万天使投资。
  4. SegmentFault 在 2014 年获得 IDG资本 数百万天使投资。
  5. SegmentFault 在 2015 年获得顶级 VC 赛富亚洲基金(软银赛富)领投、IDG资本 跟投的数千万 A 轮融资。

网站架构

SegmentFault 基于我们自己开发的 Typecho Framework 开源框架,这是一个简单、轻量、可扩展的 PHP 框架。其中引入了类似 JAVA 注入变量的概念,解决了 PHP 项目中模块的自由引用问题。存储采用 Redis、MySQL,搜索引擎选用 xunsearch,前端为响应式设计,使用了 Sass、Compass、jQuery 等技术。整个项目通过 GitHub、BaseCamp、Gmail 进行协作。

关注 53052

Sourcelink 关注了标签 · 2018-11-20

区块链

区块链(英语:Blockchain 或 Block chain)是一种分布式数据库,起源自比特币。区块链是一串使用密码学方法相关联产生的数据块,每一个数据块中包含了一次比特币网络交易的信息,用于验证其信息的有效性(防伪)和生成下一个区块。该概念在中本聪的白皮书中提出,中本聪创造第一个区块,即“创世区块”。

区块链在网络上是公开的,可以在每一个离线比特币钱包数据中查询。比特币钱包的功能依赖于与区块链的确认,一次有效检验称为一次确认。通常一次交易要获得数个确认才能进行。轻量级比特币钱包使用在线确认,即不会下载区块链数据到设备存储中。

比特币的众多竞争币也使用同样的设计,只是在工作量证明上和算法上略有不同。如,采用权益证明和 SCrypt 算法等等。

关注 51199

Sourcelink 关注了标签 · 2018-11-20

关注 65929

Sourcelink 关注了标签 · 2018-11-20

关注 84537

Sourcelink 关注了标签 · 2018-11-20

android

Android(安卓或安致)是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

简介

  Android一词的本义指“机器人”,同时也是Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统、中间件、用户界面和应用软件组成。 

  系统架构

  android的系统架构和其操作系统一样,采用了分层的架构。从架构图看,android分为四个层,从高层到低层分别是应用程序层、应用程序框架层、系统运行库层和linux核心层。

  应用程序

  Android会同一系列核心应用程序包一起发布,该应用程序包包括客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等。所有的应用程序都是使用JAVA语言编写的。

  应用程序框架

  开发人员也可以完全访问核心应用程序所使用的API框架。该应用程序的架构设计简化了组件的重用;任何一个应用程序都可以发布它的功能块并且任何其它的应用程序都可以使用其所发布的功能块(不过得遵循框架的安全性)。同样,该应用程序重用机制也使用户可以方便的替换程序组件。

  隐藏在每个应用后面的是一系列的服务和系统, 其中包括;

  丰富而又可扩展的视图(Views),可以用来构建应用程序, 它包括列表(lists),网格(grids),文本框(text boxes),按钮(buttons), 甚至可嵌入的web浏览器。

  内容提供器(Content Providers)使得应用程序可以访问另一个应用程序的数据(如联系人数据库), 或者共享它们自己的数据

  资源管理器(Resource Manager)提供 非代码资源的访问,如本地字符串,图形,和布局文件( layout files )。

  通知管理器 (Notification Manager) 使得应用程序可以在状态栏中显示自定义的提示信息。

  活动管理器( Activity Manager) 用来管理应用程序生命周期并提供常用的导航回退功能。

  有关更多的细节和怎样从头写一个应用程序,请参考 如何编写一个 Android 应用程序。

  系统运行库

  Android 包含一些C/C++库,这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。以下是一些核心库:

  * 系统 C 库 - 一个从BSD继承来的标准 C 系统函数库( libc ), 它是专门为基于 embedded linux的设备定制的。

  * 媒体库 - 基于PacketVideo OpenCORE;该库支持多种常用的音频、视频格式回放和录制,同时支持静态图像文件。编码格式包括MPEG4, H.264, MP3, AAC, AMR, JPG, PNG 。

  * Surface Manager - 对显示子系统的管理,并且为多个应用程序提 供了2D和3D图层的无缝融合。

  * LibWebCore - 一个最新的web浏览器引擎用,支持Android浏览器和一个可嵌入的web视图。

  应用程序组件

  Android开发四大组件分别是:活动(Activity): 用于表现功能。服务(Service): 后台运行服务,不提供界面呈现。广播接收器(BroadcastReceiver):用于接收广播。内容提供商(Content Provider): 支持在多个应用中存储和读取数据,相当于数据库。

  活动

  Android 中,Activity 是所有程序的根本,所有程序的流程都运行在Activity 之中,Activity可以算是开发者遇到的最频繁,也是Android 当中最基本的模块之一。在Android的程序当中,Activity 一般代表手机屏幕的一屏。如果把手机比作一个浏览器,那么Activity就相当于一个网页。在Activity 当中可以添加一些Button、Check box 等控件。可以看到Activity 概念和网页的概念相当类似。

  一般一个Android 应用是由多个Activity 组成的。这多个Activity 之间可以进行相互跳转,例如,按下一个Button 按钮后,可能会跳转到其他的Activity。和网页跳转稍微有些不一样的是,Activity 之间的跳转有可能返回值,例如,从Activity A 跳转到Activity B,那么当Activity B 运行结束的时候,有可能会给Activity A 一个返回值。这样做在很多时候是相当方便的。

  当打开一个新的屏幕时,之前一个屏幕会被置为暂停状态,并且压入历史堆栈中。用户可以通过回退操作返回到以前打开过的屏幕。我们可以选择性的移除一些没有必要保留的屏幕,因为Android会把每个应用的开始到当前的每个屏幕保存在堆栈中。

  服务

  Service 是android 系统中的一种组件,它跟Activity 的级别差不多,但是他不能自己运行,只能后台运行,并且可以和其他组件进行交互。Service 是没有界面的长生命周期的代码。Service 是一种程序,它可以运行很长时间,但是它却没有用户界面。这么说有点枯燥,来看个例子。打开一个音乐播放器的程序,这个时候若想上网了,那么,我们打开Android 浏览器,这个时候虽然我们已经进入了浏览器这个程序,但是,歌曲播放并没有停止,而是在后台继续一首接着一首的播放。其实这个播放就是由播放音乐的Service进行控制。当然这个播放音乐的Service也可以停止,例如,当播放列表里边的歌曲都结束,或者用户按下了停止音乐播放的快捷键等。service 可以在和多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD 卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务嘛,总是藏在后头的。

  开启service有两种方式:

  (1) Context.startService():Service会经历onCreate -> onStart(如果Service还没有运行,则android先调用onCreate()然后调用onStart();如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次 );stopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。该Service的调用者再启动起来后可以通过stopService关闭Service。 注意,多次调用Context.startservice()不会嵌套(即使会有相应的onStart()方法被调用),所以无论同一个服务被启动了多少次,一旦调用Context.stopService()或者stopSelf(),他都会被停止。补充说明:传递给startService()的Intent对象会传递给onStart()方法。调用顺序为:onCreate --> onStart(可多次调用) --> onDestroy。

  (2) Context.bindService():Service会经历onCreate() --> onBind(),onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind --> onDestroyed相应退出,所谓绑定在一起就共存亡了。[20]

  广播接收器

  在Android 中,Broadcast 是一种广泛运用的在应用程序之间传输信息的机制。而BroadcastReceiver 是对发送出来的Broadcast进行过滤接受并响应的一类组件。可以使用BroadcastReceiver 来让应用对一个外部的事件做出响应。这是非常有意思的,例如,当电话呼入这个外部事件到来的时候,可以利用BroadcastReceiver 进行处理。例如,当下载一个程序成功完成的时候,仍然可以利用BroadcastReceiver 进行处理。BroadcastReceiver不能生成UI,也就是说对于用户来说不是透明的,用户是看不到的。BroadcastReceiver通过NotificationManager 来通知用户这些事情发生了。BroadcastReceiver 既可以在AndroidManifest.xml 中注册,也可以在运行时的代码中使用Context.registerReceiver()进行注册。只要是注册了,当事件来临的时候,即使程序没有启动,系统也在需要的时候启动程序。各种应用还可以通过使用Context.sendBroadcast () 将它们自己的intent broadcasts广播给其他应用程序。

  注册BroadcastReceiver有两种方式:

  (1)在AndroidManifest.xml进行注册。这种方法有一个特点即使你的应用程序已经关闭了,但这个BroadcastReceiver依然会接受广播出来的对象,也就是说无论你这个应用程序时开还是关都属于活动状态都可以接受到广播的事件;

  (2)在代码中注册广播。

  第一种俗称静态注册,第二种俗称动态注册,这两种注册Broadcast Receiver的区别:

  动态注册较静态注册灵活。实验证明:当静态注册一个Broadcast Receiver时,不论应用程序是启动与否。都可以接受对应的广播。

  动态注册的时候,如果不执行unregister Receiver();方法取消注册,跟静态是一样的。但是如果执行该方法,当执行过以后,就不能接受广播了。

  内容提供

  Content Provider 是Android提供的第三方应用数据的访问方案。

  在Android中,对数据的保护是很严密的,除了放在SD卡中的数据,一个应用所持有的数据库、文件等内容,都是不允许其他直接访问的。Andorid当然不会真的把每个应用都做成一座孤岛,它为所有应用都准备了一扇窗,这就是Content Provider。应用想对外提供的数据,可以通过派生Content Provider类, 封装成一枚Content Provider,每个Content Provider都用一个uri作为独立的标识,形如:content://com.xxxxx。所有东西看着像REST的样子,但实际上,它比REST 更为灵活。和REST类似,uri也可以有两种类型,一种是带id的,另一种是列表的,但实现者不需要按照这个模式来做,给你id的uri你也可以返回列表类型的数据,只要调用者明白,就无妨,不用苛求所谓的REST。

  另外,Content Provider不和REST一样只有uri可用,还可以接受Projection,Selection,OrderBy等参数,这样,就可以像数据库那样进行投影,选择和排序。查询到的结果,以Cursor(参见:reference/android/database/Cursor.html )的形式进行返回,调用者可以移动Cursor来访问各列的数据。

  Content Provider屏蔽了内部数据的存储细节,向外提供了上述统一的接口模型,这样的抽象层次,大大简化了上层应用的书写,也对数据的整合提供了更方便的途径。Content Provider内部,常用数据库来实现,Android提供了强大的Sqlite支持,但很多时候,你也可以封装文件或其他混合的数据。

  在Android中,Content Resolver是用来发起Content Provider的定位和访问的。不过它仅提供了同步访问的Content Provider的接口。但通常,Content Provider需要访问的可能是数据库等大数据源,效率上不足够快,会导致调用线程的拥塞。因此Android提供了一个AsyncQueryHandler(参见:reference/android/content/AsyncQueryHandler.html),帮助进行异步访问Content Provider。

  在各大组件中,Service和Content Provider都是那种需要持续访问的。Service如果是一个耗时的场景,往往会提供异步访问的接口,而Content Provider不论效率如何,都提供的是约定的同步访问接口。

软件开发

  Java方面

  Android支持使用Java作为编程语言来开发应用程序,而Android的Java开发方面从接口到功能,都有层出不穷的变化。考虑到Java虚拟机的效率和资源占用,谷歌重新设计了Android的Java,以便能提高效率和减少资源占用,因而与J2ME等不同。其中Activity等同于J2ME的MIDlet,一个 Activity 类(Class)负责创建视窗(Windows),一个活动中的Activity就是在 foreground(前景)模式,背景运行的程序叫做Service。两者之间通过由ServiceConnection和AIDL连结,达到复数程序同时运行效果。如果运行中的 Activity 全部画面被其他 Activity 取代时,该 Activity 便被停止(Stopped),甚至被系统清除(Kill)。

  View等同于J2ME的Displayable,程序人员可以通过 View 类与“XML layout”档将UI放置在视窗上,Android 1.5的版本可以利用 View 打造出所谓的 Widgets,其实Widget只是View的一种,所以可以使用xml来设计layout,HTC的Android Hero手机即含有大量的widget。至于ViewGroup 是各种layout 的基础抽象类(abstract class),ViewGroup之内还可以有ViewGroup。View的构造函数不需要再Activity中调用,但是Displayable的是必须的,在Activity 中,要通过findViewById()来从XML 中取得View,Android的View类的显示很大程度上是从XML中读取的。View 与事件(event)息息相关,两者之间通过Listener 结合在一起,每一个View都可以注册一个event listener,例如:当View要处理用户触碰(touch)的事件时,就要向Android框架注册View.OnClickListener。另外还有BitMap等同于J2ME的Image。   

关注 63864

Sourcelink 发布了文章 · 2018-11-20

Binder机制情景分析之linux环境适配

binder安装

一. 环境

- 运行环境:linux4.1.15
- 开发板为天嵌imx6ul

二. 内核修改

2.1 打开内核配置菜单

make menuconfig 

2.2 修改配置

    1. 配置驱动

转到Device Drivers->Android,选中Andoid DriversAndroid Binder IPC Driver

示例如下:

clipboard.png

    1. 配置binder驱动中使用到接口

转到Device Drivers->Staging drivers->Android,选中:
Enable the Anonymous Shared Memory Subsystem,
Synchronization framework,
Software synchronization objects,
Userspace API for SW_SYNC

示例如下:

clipboard.png

2.3 重新编译

make zImage -j4 

三. 查看

将重新编译好的内核更新到开发板中;
ls命令查看/dev下是否有个设备为binder

查看原文

赞 1 收藏 1 评论 0

Sourcelink 关注了用户 · 2018-11-20

高阳Sunny @sunny

SegmentFault 思否 CEO
C14Z.Group Founder
Forbes China 30U30

独立思考 敢于否定

曾经是个话痨... 要做一个有趣的人!

任何问题可以给我发私信或者发邮件 sunny@sifou.com

关注 2119

Sourcelink 发布了文章 · 2018-11-20

Binder机制情景分析之C服务应用

一. 概述

这里只讲下binder的实现原理,不牵扯到android的java层是如何调用;
涉及到的会有ServiceManager,led_control_servertest_client的代码,这些都是用c写的.其中led_control_servertest_client
仿照bctest.c写的; 在linux平台下运行binder更容易分析binder机制实现的原理(可以增加大量的log,进行分析);
在Linux运行时.先运行ServiceManager,再运行led_control_server最后运行test_client;

1.1 Binder通信模型

Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及binder驱动,其中ServiceManager用于管理系统中的各种服务。

1.2 运行环境

本文中的代码运行环境是在imx6ul上跑的,运行的是linux系统,内核版本4.10(非android环境分析);

1.3 文章代码

文章所有代码已上传

https://github.com/SourceLink...

二. ServiceManager

涉及到的源码地址:

frameworks/native/cmds/servicemanager/sevice_manager.c
frameworks/native/cmds/servicemanager/binder.c
frameworks/native/cmds/servicemanager/bctest.c

ServiceManager相当于binder通信过程中的守护进程,本身也是个binder服务、好比一个root管理员一样;
主要功能是查询和注册服务;接下来结合代码从main开始分析下serviceManager的服务过程;

2.1 main

源码中的sevice_manager.c中主函数中使用了selinux,为了在我板子的linux环境中运行,把这些代码屏蔽,删减后如下:

int main(int argc, char **argv)
{
    struct binder_state *bs;

    bs = binder_open(128*1024);                                                ①
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }

    if (binder_become_context_manager(bs)) {                                   ②
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }


    svcmgr_handle = BINDER_SERVICE_MANAGER;
    binder_loop(bs, svcmgr_handler);                                           ③

    return 0;
}
①: 打开binder驱动(详见2.2.1)
②: 注册为管理员(详见2.2.2)
③: 进入循环,处理消息(详见2.2.3)

从主函数的启动流程就能看出sevice_manager的工作流程并不是特别复杂;
其实clientserver的启动流程和manager的启动类似,后面再详细分析;

2.2 binder_open

struct binder_state *binder_open(size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;

    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }

    bs->fd = open("/dev/binder", O_RDWR);                                     ①
    if (bs->fd < 0) {
        fprintf(stderr,"binder: cannot open device (%s)\n",
                strerror(errno));
        goto fail_open;
    }

    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||                       ②
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        fprintf(stderr, "binder: driver version differs from user space\n");
        goto fail_open;
    }

    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);      ③
    if (bs->mapped == MAP_FAILED) {
        fprintf(stderr,"binder: cannot map device (%s)\n",
                strerror(errno));
        goto fail_map;
    }

    return bs;

fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}
①: 打开binder设备
②: 通过ioctl获取binder版本号
③: mmp内存映射

这里说明下为什么binder驱动是用ioctl来操作,是因为ioctl可以同时进行读和写操作;

2.2 binder_become_context_manager

int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

还是通过ioctl请求类型BINDER_SET_CONTEXT_MGR注册成manager;

2.3 binder_loop

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;

    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));                                       ①

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;

        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);                                  ②

        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }

        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);       ③
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}
①: 写入命令BC_ENTER_LOOPER通知驱动该线程已经进入主循环,可以接收数据;
②: 先读一次数据,因为刚才写过一次;
③: 然后解析读出来的数据(详见2.2.4);

binder_loop函数的主要流程如下:

clipboard.png

2.4 binder_parse

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    int r = 1;
    uintptr_t end = ptr + (uintptr_t) size;

    while (ptr < end) {
        uint32_t cmd = *(uint32_t *) ptr;
        ptr += sizeof(uint32_t);
#if TRACE
        fprintf(stderr,"%s:\n", cmd_name(cmd));
#endif
        switch(cmd) {
        case BR_NOOP:
            break;
        case BR_TRANSACTION_COMPLETE:
            /* check服务 */
            break;
        case BR_INCREFS:
        case BR_ACQUIRE:
        case BR_RELEASE:
        case BR_DECREFS:
#if TRACE
            fprintf(stderr,"  %p, %p\n", (void *)ptr, (void *)(ptr + sizeof(void *)));
#endif
            ptr += sizeof(struct binder_ptr_cookie);
            break;
        case BR_SPAWN_LOOPER: {
            /* create new thread */
            //if (fork() == 0) {
            //}
            pthread_t thread;
            struct binder_thread_desc btd;

            btd.bs = bs;
            btd.func = func;
            
            pthread_create(&thread, NULL, binder_thread_routine, &btd);

            /* in new thread: ioctl(BC_ENTER_LOOPER), enter binder_looper */

            break;
        }
        case BR_TRANSACTION: {
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            if ((end - ptr) < sizeof(*txn)) {
                ALOGE("parse: txn too small!\n");
                return -1;
            }
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;

                bio_init(&reply, rdata, sizeof(rdata), 4);                               ①
                bio_init_from_txn(&msg, txn);
                res = func(bs, txn, &msg, &reply);                                       ②
                binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);                ③
            }
            ptr += sizeof(*txn);
            break;
        }
        case BR_REPLY: {
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            if ((end - ptr) < sizeof(*txn)) {
                ALOGE("parse: reply too small!\n");
                return -1;
            }
            binder_dump_txn(txn);
            if (bio) {
                bio_init_from_txn(bio, txn);
                bio = 0;
            } else {
                /* todo FREE BUFFER */
            }
            ptr += sizeof(*txn);
            r = 0;
            break;
        }
        case BR_DEAD_BINDER: {
            struct binder_death *death = (struct binder_death *)(uintptr_t) *(binder_uintptr_t *)ptr;
            ptr += sizeof(binder_uintptr_t);
            death->func(bs, death->ptr);
            break;
        }
        case BR_FAILED_REPLY:
            r = -1;
            break;
        case BR_DEAD_REPLY:
            r = -1;
            break;
        default:
            ALOGE("parse: OOPS %d\n", cmd);
            return -1;
        }
    }

    return r;
}
①: 按照一定的格式初始化rdata数据,请注意这里rdata是在用户空间创建的buf;
②: 调用设置进来的处理函数svcmgr_handler(详见2.2.5);
③: 发送回复信息;

这个函数我们只重点关注下BR_TRANSACTION其他的命令含义可以参考表格A;

2.5 svcmgr_handler

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;

    //ALOGI("target=%x code=%d pid=%d uid=%d\n",
    //  txn->target.handle, txn->code, txn->sender_pid, txn->sender_euid);

    if (txn->target.handle != svcmgr_handle)
        return -1;

    if (txn->code == PING_TRANSACTION)
        return 0;

    // Equivalent to Parcel::enforceInterface(), reading the RPC
    // header with the strict mode policy mask and the interface name.
    // Note that we ignore the strict_policy and don't propagate it
    // further (since we do no outbound RPCs anyway).
    strict_policy = bio_get_uint32(msg);                                           ①
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return -1;
    }

    if ((len != (sizeof(svcmgr_id) / 2)) ||                                        ②
        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
        fprintf(stderr,"invalid id %s\n", str8(s, len));
        return -1;
    }


    switch(txn->code) {                                                            ③
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid);   ④
        if (!handle)
            break;
        bio_put_ref(reply, handle);
        return 0;

    case SVC_MGR_ADD_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint32(msg) ? 1 : 0;
        if (do_add_service(bs, s, len, handle, txn->sender_euid,                   ⑤
            allow_isolated, txn->sender_pid))
            return -1;
        break;

    case SVC_MGR_LIST_SERVICES: {
        uint32_t n = bio_get_uint32(msg);

        if (!svc_can_list(txn->sender_pid)) {
            ALOGE("list_service() uid=%d - PERMISSION DENIED\n",
                    txn->sender_euid);
            return -1;
        }
        si = svclist;
        while ((n-- > 0) && si)                                                    ⑥
            si = si->next;
        if (si) {
            bio_put_string16(reply, si->name);
            return 0;
        }
        return -1;
    }
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }

    bio_put_uint32(reply, 0);
    return 0;
}
①: 获取帧头数据,一般为0,因为发送方发送数据时都会在数据最前方填充4个字节0数据(分配数据空间的最小单位4字节);
②: 对比svcmgr_id是否和我们原来定义相同#define SVC_MGR_NAME "linux.os.ServiceManager"(我改写了);
③: 根据code 做对应的事情,就想到与根据编码去执行对应的fun(client请求服务后去执行服务,service也是根据不同的code来执行。接下来会举例说明);、
④: 从服务名在server链表中查找对应的服务,并返回handle(详见2.2.6);
⑤: 添加服务,一般都是service发起的请求。将handle和服务名添加到服务链表中(这里的handle是由binder驱动分配);
⑥: 查找server_manager中链表中第n个服务的名字(该数值由查询端决定);

2.6 do_find_service

uint32_t do_find_service(struct binder_state *bs, const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
    struct svcinfo *si;

    if (!svc_can_find(s, len, spid)) {                                             ①
        ALOGE("find_service('%s') uid=%d - PERMISSION DENIED\n",
             str8(s, len), uid);
        return 0;
    }
    si = find_svc(s, len);                                                         ②
    //ALOGI("check_service('%s') handle = %x\n", str8(s, len), si ? si->handle : 0);
    if (si && si->handle) {
        if (!si->allow_isolated) {                                                 ③
            // If this service doesn't allow access from isolated processes,
            // then check the uid to see if it is isolated.
            uid_t appid = uid % AID_USER;
            if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {
                return 0;
            }
        }
        return si->handle;                                                         ④
    } else {
        return 0;
    }
}
①: 检测调用进程是否有权限请求服务(这里用selinux管理权限,为了让代码可以方便允许,这里面的代码有做删减);
②: 遍历server_manager服务链表;
③: 如果binder服务不允许服务从沙箱中访问,则执行下面检查;
④: 返回查询到handle;

do_find_service函数主要工作是搜索服务链表,返回查找到的服务

2.7 do_add_service

int do_add_service(struct binder_state *bs,
                   const uint16_t *s, size_t len,
                   uint32_t handle, uid_t uid, int allow_isolated,
                   pid_t spid)
{
    struct svcinfo *si;

    //ALOGI("add_service('%s',%x,%s) uid=%d\n", str8(s, len), handle,
    //        allow_isolated ? "allow_isolated" : "!allow_isolated", uid);

    if (!handle || (len == 0) || (len > 127))
        return -1;

    if (!svc_can_register(s, len, spid)) {                                     ①
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
             str8(s, len), handle, uid);
        return -1;
    }

    si = find_svc(s, len);                                                     ②
    if (si) {
        if (si->handle) {
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",
                 str8(s, len), handle, uid);
            svcinfo_death(bs, si);
        }
        si->handle = handle;
    } else {                                                                   ③
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",
                 str8(s, len), handle, uid);
            return -1;
        }
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->next = svclist;
        svclist = si;
    }

    ALOGI("add_service('%s'), handle = %d\n", str8(s, len), handle);

    binder_acquire(bs, handle);                                               ④
    binder_link_to_death(bs, handle, &si->death);                             ⑤
    return 0;
}
①: 判断请求进程是否有权限注册服务;
②: 查找ServiceManager的服务链表中是否已经注册了该服务,如果有则通知驱动杀死原先的binder服务,然后更新最新的binder服务;
③: 如果原来没有创建该binder服务,则进行一系列的赋值,再插入到服务链表的表头;
④: 增加binder服务的引用计数;
⑤: 告诉驱动接收服务的死亡通知;

2.8 调用时序图

从上面分析,可以知道ServiceManager的主要工作流程如下:

clipboard.png

三. led_control_server

3.1 main

int main(int argc, char **argv) 
{
    int fd;
    struct binder_state *bs;
    uint32_t svcmgr = BINDER_SERVICE_MANAGER;
    uint32_t handle;
      int ret;

    struct register_server  led_control[3] = {                                ①
        [0] = {
            .code = 1,
            .fun = led_on
        } , 
        [1] = {
            .code = 2,
            .fun = led_off
        }
    };

    
    bs = binder_open(128*1024);                                               ②
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }

    
    ret = svcmgr_publish(bs, svcmgr, LED_CONTROL_SERVER_NAME, led_control);   ③

    if (ret) {
        ALOGE("failed to publish %s service\n", LED_CONTROL_SERVER_NAME);
        return -1;
    }

    binder_set_maxthreads(bs, 10);                                            ④

    binder_loop(bs, led_control_server_handler);                              ⑤

    return 0;
}
①: led_control_server提供的服务函数;
②: 初始化binder组件( 详见2.2);
③: 注册服务,svcmgr是发送的目标, LED_CONTROL_SERVER_NAME注册的服务名, led_control注册的binder实体;
④: 设置创建线程最大数(详见3.5);
⑤: 进入线程循环(详见2.3);

3.2 svcmgr_publish

int svcmgr_publish(struct binder_state *bs, uint32_t target, const char *name, void *ptr)
{
    int status;
    unsigned iodata[512/4];
    struct binder_io msg, reply;

    bio_init(&msg, iodata, sizeof(iodata), 4);                                ①
    bio_put_uint32(&msg, 0);  // strict mode header
    bio_put_string16_x(&msg, SVC_MGR_NAME);
    bio_put_string16_x(&msg, name);
    bio_put_obj(&msg, ptr);

    if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE))           ②
        return -1;

    status = bio_get_uint32(&reply);                                          ③

    binder_done(bs, &msg, &reply);                                            ④

    return status;
}
①: 初始化用户空间的数据iodata,设置了四个字节的offs,接着按一定格式往buf里面填充数据;
②: 调用ServiceManager服务的SVC_MGR_ADD_SERVICE功能;
③: 获取ServiceManager回复数据,成功返回0;
④: 结束注册过程,释放内核中刚才交互分配的buf;

3.2.1 bio_init

void bio_init(struct binder_io *bio, void *data,
              size_t maxdata, size_t maxoffs)
{
    size_t n = maxoffs * sizeof(size_t);

    if (n > maxdata) {
        bio->flags = BIO_F_OVERFLOW;
        bio->data_avail = 0;
        bio->offs_avail = 0;
        return;
    }

    bio->data = bio->data0 = (char *) data + n;                               ①
    bio->offs = bio->offs0 = data;                                            ②
    bio->data_avail = maxdata - n;                                            ③
    bio->offs_avail = maxoffs;                                                ④
    bio->flags = 0;                                                           ⑤
}
①: 根据传进来的参数,留下一定长度的offs数据空间, data指针则从 data + n开始;
②: offs指针则从 data开始,则offs可使用的数据空间只有n个字节;
③: 可使用的data空间计数;
④: 可使用的offs空间计数;
⑤: 清除buf的flag;

init后此时buf空间的分配情况如下图:

3.2.2 bio_put_uint32

void bio_put_uint32(struct binder_io *bio, uint32_t n)
{
    uint32_t *ptr = bio_alloc(bio, sizeof(n));
    if (ptr)
        *ptr = n;
}

这个函数往buf里面填充一个uint32的数据,这个数据的最小单位为4个字节;
前面svcmgr_publish调用bio_put_uint32(&msg, 0);,实质buf中的数据是00 00 00 00 ;

3.2.3 bio_alloc

static void *bio_alloc(struct binder_io *bio, size_t size)
{
    size = (size + 3) & (~3);
    if (size > bio->data_avail) {
        bio->flags |= BIO_F_OVERFLOW;
        return NULL;
    } else {
        void *ptr = bio->data;
        bio->data += size;
        bio->data_avail -= size;
        return ptr;
    }
}

这个函数分配的数据宽度为4的倍数,先判断当前可使用的数据宽度是否小于待分配的宽度;
如果小于则置标志BIO_F_OVERFLOW否则分配数据,并对data往后偏移size个字节,可使用数据宽度data_avail减去size个字节;

3.2.4 bio_put_string16_x

void bio_put_string16_x(struct binder_io *bio, const char *_str)
{
    unsigned char *str = (unsigned char*) _str;
    size_t len;
    uint16_t *ptr;

    if (!str) {                                                            ①
        bio_put_uint32(bio, 0xffffffff);
        return;
    }

    len = strlen(_str);

    if (len >= (MAX_BIO_SIZE / sizeof(uint16_t))) {
        bio_put_uint32(bio, 0xffffffff);
        return;
    }

    /* Note: The payload will carry 32bit size instead of size_t */
    bio_put_uint32(bio, len);                                             ②
    ptr = bio_alloc(bio, (len + 1) * sizeof(uint16_t));
    if (!ptr)
        return;

    while (*str)                                                          ③
        *ptr++ = *str++;
    *ptr++ = 0;
}
①: 这里到bio_alloc前都是为了计算和判断自己串的长度再填充到buf中;
②: 填充字符串前会填充字符串的长度;
③: 填充字符串到buf中,一个字符占两个字节,注意 uint16_t *ptr;;

3.2.5 bio_put_obj

void bio_put_obj(struct binder_io *bio, void *ptr)
{
    struct flat_binder_object *obj;

    obj = bio_alloc_obj(bio);                                             ①
    if (!obj)
        return;

    obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    obj->type = BINDER_TYPE_BINDER;                                       ②
    obj->binder = (uintptr_t)ptr;                                         ③
    obj->cookie = 0;
}
struct flat_binder_object {
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
 __u32 type;
 __u32 flags;
 union {
 binder_uintptr_t binder;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
 __u32 handle;
 };
 binder_uintptr_t cookie;
};
①: 分配一个flat_binder_object大小的空间(详见3.2.6);
②: type的类型为BINDER_TYPE_BINDER时则type传入的是binder实体,一般是服务端注册服务时传入;
type的类型为BINDER_TYPE_HANDLE时则type传入的为handle,一般由客户端请求服务时;
③: obj->binder值,跟随type改变;

3.2.6 bio_alloc_obj

static struct flat_binder_object *bio_alloc_obj(struct binder_io *bio)
{
    struct flat_binder_object *obj;

    obj = bio_alloc(bio, sizeof(*obj));                                    ①

    if (obj && bio->offs_avail) {
        bio->offs_avail--;
        *bio->offs++ = ((char*) obj) - ((char*) bio->data0);               ②
        return obj;
    }

    bio->flags |= BIO_F_OVERFLOW;
    return NULL;
}
①: 在data后分配struct flat_binder_object长度的空间;
②: bio->offs空间记下此时插入obj,相对于data0的偏移值;

看到这终于知道offs是干嘛的了,原来是用来记录数据中是否有obj类型的数据;

3.2.7 完整数据格式图

综上分析,传输一次完整的数据的格式如下:

clipboard.png

3.3 binder_call

int binder_call(struct binder_state *bs,
                struct binder_io *msg, struct binder_io *reply,
                uint32_t target, uint32_t code)
{
    int res;
    struct binder_write_read bwr;
    struct {
        uint32_t cmd;
        struct binder_transaction_data txn;
    } __attribute__((packed)) writebuf;
    unsigned readbuf[32];

    if (msg->flags & BIO_F_OVERFLOW) {
        fprintf(stderr,"binder: txn buffer overflow\n");
        goto fail;
    }

    writebuf.cmd = BC_TRANSACTION;   // binder call transaction 
    writebuf.txn.target.handle = target;                                               ①
    writebuf.txn.code = code;                                                          ②
    writebuf.txn.flags = 0;
    writebuf.txn.data_size = msg->data - msg->data0;                                   ③
    writebuf.txn.offsets_size = ((char*) msg->offs) - ((char*) msg->offs0);
    writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0;
    writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0;

    bwr.write_size = sizeof(writebuf);                                                 ④
    bwr.write_consumed = 0;
    bwr.write_buffer = (uintptr_t) &writebuf;

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;

        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);                                  ⑤

        if (res < 0) {
            fprintf(stderr,"binder: ioctl failed (%s)\n", strerror(errno));
            goto fail;
        }

        res = binder_parse(bs, reply, (uintptr_t) readbuf, bwr.read_consumed, 0);      ⑥
        if (res == 0) return 0;
        if (res < 0) goto fail;
    }

fail:
    memset(reply, 0, sizeof(*reply));
    reply->flags |= BIO_F_IOERROR;
    return -1;
}
①: 这个target就是我们这次请求服务的目标,即ServiceManager;
②: code是我们请求服务的功能码,由服务端提供;
③: 把binder_io数据转化成binder_transaction_data数据;
④: 驱动进行读写是根据这个size来的,分析驱动的时候再详细分析;
⑤: 进行一次读写;
⑥: 解析发送的后返回的数据,判断是否注册成功;

3.4 binder_done

void binder_done(struct binder_state *bs,
                 struct binder_io *msg,
                 struct binder_io *reply)
{
    struct {
        uint32_t cmd;
        uintptr_t buffer;
    } __attribute__((packed)) data;

    if (reply->flags & BIO_F_SHARED) {
        data.cmd = BC_FREE_BUFFER;
        data.buffer = (uintptr_t) reply->data0;
        binder_write(bs, &data, sizeof(data));
        reply->flags = 0;
    }
}

这个函数比较简单发送BC_FREE_BUFFER命令给驱动,让驱动释放内核态由刚才交互分配的buf;

3.5 binder_set_maxthreads

void binder_set_maxthreads(struct binder_state *bs, int threads)
{
    ioctl(bs->fd, BINDER_SET_MAX_THREADS, &threads);
}

这里主要调用ioctl函数写入命令BINDER_SET_MAX_THREADS进行设置最大线程数;

3.6 调用时序图

led_control_server主要提供led的控制服务,具体的流程如下:

clipboard.png

四. test_client

4.1 main

int main(int argc, char **argv)
{
    struct binder_state *bs;
    uint32_t svcmgr = BINDER_SERVICE_MANAGER;
    unsigned int g_led_control_handle;

    if (argc < 3) {
        ALOGE("Usage:\n");
        ALOGE("%s led <on|off>\n", argv[0]);
        return -1;
    }

    bs = binder_open(128*1024);                                                        ①
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }

    g_led_control_handle = svcmgr_lookup(bs, svcmgr, LED_CONTROL_SERVER_NAME);         ②
    if (!g_led_control_handle) {
        ALOGE( "failed to get led control service\n");
        return -1;
    }

    ALOGI("Handle for led control service = %d\n", g_led_control_handle);

    if (!strcmp(argv[1], "led")) {
        if (!strcmp(argv[2], "on")) {
            if (interface_led_on(bs, g_led_control_handle, 2) == 0) {                  ③
                ALOGI("led was on\n");
            }
        } else if (!strcmp(argv[2], "off")) {
            if (interface_led_off(bs, g_led_control_handle, 2) == 0) {
                ALOGI("led was off\n");
            }
        }
    }

    binder_release(bs, g_led_control_handle);                                          ④

    return 0;
}
①: 打开binder设备(详见2.2);
②: 根据名字获取led控制服务;
③: 根据获取到的handle,调用led控制服务(详见4.3);
④: 释放服务;

client的流程也很简单,按步骤1.2.3.4读下来就是了;

4.2 svcmgr_lookup

uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)
{
    uint32_t handle;
    unsigned iodata[512/4];
    struct binder_io msg, reply;

    bio_init(&msg, iodata, sizeof(iodata), 4);                                         ①
    bio_put_uint32(&msg, 0);  // strict mode header
    bio_put_string16_x(&msg, SVC_MGR_NAME);
    bio_put_string16_x(&msg, name);

    if (binder_call(bs, &msg, &reply, target, SVC_MGR_GET_SERVICE))                    ②
        return 0;

    handle = bio_get_ref(&reply);                                                      ③

    if (handle)
        binder_acquire(bs, handle);                                                    ④

    binder_done(bs, &msg, &reply);                                                     ⑤

    return handle;
}
①: 因为是请求服务,所以这里不用添加binder实体数据,具体的参考3.2,这里就不重复解释了;
②: 向target进程(ServiceManager)请求获取led_control服务(详细参考3.3);
③: 从ServiceManager返回的数据buf中获取led_control服务的handle;
④: 增加该handle的引用计数;
⑤: 释放内核空间buf(详3.4);

4.2.1 bio_get_ref

uint32_t bio_get_ref(struct binder_io *bio)
{
    struct flat_binder_object *obj;

    obj = _bio_get_obj(bio);                                                          ①
    if (!obj)
        return 0;

    if (obj->type == BINDER_TYPE_HANDLE)                                              ②
        return obj->handle;

    return 0;
}
①: 把bio的数据转化成flat_binder_object格式;
②: 判断binder数据类型是否为引用,是则返回获取到的handle;

4.2.2 _bio_get_obj

static struct flat_binder_object *_bio_get_obj(struct binder_io *bio)
{
    size_t n;
    size_t off = bio->data - bio->data0;                                              ①
    /* TODO: be smarter about this? */
    for (n = 0; n < bio->offs_avail; n++) {
        if (bio->offs[n] == off)
            return bio_get(bio, sizeof(struct flat_binder_object));                   ②
    }

    bio->data_avail = 0;
    bio->flags |= BIO_F_OVERFLOW;
    return NULL;
}
①: 一般情况下该值都为0,因为在reply时获取ServiceManager传来的数据,bio->data和bio->data都指向同一个地址;
②: 获取到struct flat_binder_object数据的头指针;

从ServiceManager传来的数据是struct flat_binder_object的数据,格式如下:

4.3 interface_led_on

int interface_led_on(struct binder_state *bs, unsigned int handle, unsigned char led_enum)
{
    unsigned iodata[512/4];
    struct binder_io msg, reply;
    int ret = -1;
    int exception;

    bio_init(&msg, iodata, sizeof(iodata), 4);
    bio_put_uint32(&msg, 0);  // strict mode header
    bio_put_uint32(&msg, led_enum);

    if (binder_call(bs, &msg, &reply, handle, LED_CONTROL_ON))
        return ret;

    exception = bio_get_uint32(&reply);
    if (exception == 0)
        ret = bio_get_uint32(&reply);

    binder_done(bs, &msg, &reply);

    return ret;
}

这个流程和前面svcmgr_lookup的请求服务差不多,只是最后是获取led_control_server的返回值.
注意这里为什么获取了两次uint32类型的数据,这是因为服务方在回复数据的时候添加了头帧,这个是可以调节的,非规则;

4.4 binder_release

void binder_release(struct binder_state *bs, uint32_t target)
{
    uint32_t cmd[2];
    cmd[0] = BC_RELEASE;
    cmd[1] = target;
    binder_write(bs, cmd, sizeof(cmd));
}

通知驱动层减小对target进程的引用,结合驱动讲解就更能明白了;

4.5 调用时序图

test_client的调用时序如下,过程和led_control_server的调用过程相识:

clipboard.png

A: 表BR_含义

BR个人理解是缩写为binder reply

消息含义参数
BR_ERROR发生内部错误(如内存分配失败)---
BR_OK
BR_NOOP
操作完成---
BR_SPAWN_LOOPER该消息用于接收方线程池管理。当驱动发现接收方所有
线程都处于忙碌状态且线程池里的线程总数没有超过
BINDER_SET_MAX_THREADS设置的最大线程数时,
向接收方发送该命令要求创建更多线程以备接收数据。
---
BR_TRANSACTION对应发送方的BC_TRANSACTIONbinder_transaction_data
BR_REPLY对应发送方BC_REPLY的回复binder_transaction_data
BR_ACQUIRE_RESULT
BR_FINISHED
未使用---
BR_DEAD_REPLY交互时向驱动发送binder调用,如果对方已经死亡,则
驱动回应此命令
---
BR_TRANSACTION_COMPLETE发送方通过BC_TRANSACTION或BC_REPLY发送
完一个数据包后,都能收到该消息做为成功发送的反馈。
这和BR_REPLY不一样,是驱动告知发送方已经发送成
功,而不是Server端返回请求数据。所以不管
同步还是异步交互接收方都能获得本消息。
---
BR_INCREFS
BR_ACQUIRE
BR_RELEASE
BR_DECREFS
这一组消息用于管理强/弱指针的引用计数。只有
提供Binder实体的进程才能收到这组消息。
binder_uintptr_t binder:Binder实体在用户空间中的指针
binder_uintptr_t cookie:与该实体相关的附加数据
BR_DEAD_BINDER
向获得Binder引用的进程发送Binder实体
死亡通知书;收到死亡通知书的进程接下
来会返回BC_DEAD_BINDER_DONE做确认。
---
BR_CLEAR_DEATH_NOTIFICATION_DONE回应命令BC_REQUEST_DEATH_NOTIFICATION---
BR_FAILED_REPLY如果发送非法引用号则返回该消息---

B: 表BC_含义

BC个人理解是缩写为binder call or cmd

消息含义参数
BC_TRANSACTION
BC_REPLY
BC_TRANSACTION用于Client向Server发送请求数据;
BC_REPLY用于Server向Client发送回复(应答)数据。
其后面紧接着一个binder_transaction_data结构体表明要写
入的数据。
struct binder_transaction_data
BC_ACQUIRE_RESULT
BC_ATTEMPT_ACQUIRE
未使用---
BC_FREE_BUFFER请求驱动释放调刚在内核空间创建用来保存用户空间数据的内存块---
BC_INCREFS
BC_ACQUIRE
BC_RELEASE
BC_DECREFS
这组命令增加或减少Binder的引用计数,用以实现强指针或
弱指针的功能。
---
BC_INCREFS_DONE
BC_ACQUIRE_DONE
第一次增加Binder实体引用计数时,驱动向Binder
实体所在的进程发送BR_INCREFS, BR_ACQUIRE消息;
Binder实体所在的进程处理完毕回馈BC_INCREFS_DONE,
BC_ACQUIRE_DONE
---
BC_REGISTER_LOOPER
BC_ENTER_LOOPER
BC_EXIT_LOOPER
这组命令同BINDER_SET_MAX_THREADS一道实现Binder驱
动对接收方线程池管理。BC_REGISTER_LOOPER通知驱动线程
池中一个线程已经创建了;BC_ENTER_LOOPER通知驱动该线程
已经进入主循环,可以接收数据;BC_EXIT_LOOPER通知驱动
该线程退出主循环,不再接收数据。
---
BC_REQUEST_DEATH_NOTIFICATION获得Binder引用的进程通过该命令要求驱动在Binder实体销毁得到
通知。虽说强指针可以确保只要有引用就不会销毁实体,但这毕竟
是个跨进程的引用,谁也无法保证实体由于所在的Server关闭Binder
驱动或异常退出而消失,引用者能做的是要求Server在此刻给出通知。
---
BC_DEAD_BINDER_DONE收到实体死亡通知书的进程在删除引用后用本命令告知驱动。---

参考

表格参考博客:

查看原文

赞 5 收藏 3 评论 0

Sourcelink 发布了文章 · 2018-11-18

Handler机制情景分析

一. 概述

在整个Android的源码世界里,有两大利剑,其一是Binder IPC机制,,另一个便是消息机制(由Handler/Looper/MessageQueue等构成的).
Android有大量的消息驱动方式来进行交互,比如Android的四剑客Activity, Service, Broadcast, ContentProvider的启动过程的交互,都离不开消息机制,Android某种意义上也可以说成是一个以消息驱动的系统。消息机制涉及MessageQueue/Message/Looper/Handler这4个类。

1.1 模型

消息机制主要包含:

  • Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
  • MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
  • Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
  • Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。

1.2 架构图

clipboard.png

1.3 Demo

public class MainActivity extends AppCompatActivity {

    private Button mButton;
    private final String TAG="MessageTest";
    private int ButtonCount = 0;
    private MyThread myThread;
    private Handler mHandler;
    private int mMessageCount = 0;

    class MyThread extends Thread {
        private Looper mLooper;
        @Override
        public void run() {
            super.run();
            /* Initialize the current thread as a looper */
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            /* Run the message queue in this thread */
            Looper.loop();
        }

        public Looper getLooper(){
            if (!isAlive()) {
                return null;
            }

            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton = (Button)findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // Perform action on click
                Log.d(TAG, "Send Message "+ ButtonCount);
                ButtonCount++;
                /* 按下按键后通过mHandler发送一个消息 */
                Message msg = new Message();
                mHandler.sendMessage(msg);
            }
        });

        myThread = new MyThread();
        myThread.start();

        /* 创建一个handle实例(详见4.3.2),这个handle为线程myThread服务,当收到mesg时会调用设置的回调函数*/
        mHandler = new Handler(myThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.d(TAG, "get Message "+ mMessageCount);
                mMessageCount++;
                return false;
            }
        });
    }
}

大概流程:先创建的一个线程,该线程中调用了Looper.prepare()(详见2.1)和Looper.loop()(详见2.2)方法,接着启动了该线程,紧接着初始化了一个Handler实例(详见4.3.2).用于服务message,在按下按键后通过mHandler发送了一个消息(详见4.2),此时handleMessage被回调(详见4.1).接下来进行详细分析.

该Demo中有个两点getLooper方法,当外界调用该方法时,他会判断当前mLooper是否为空,空的话就会一直等待.
为什么要这么做?
因为在创建线程后去获取mLooper,此时线程的run方法可能还为运行,所以此时mLooper值应该为null;
当运行了Looper.prepare()方法创建了looper后,通过Looper.myLooper()获取到mLooper,再notifyAll;

二. Looper

2.1 Looper.prepare()

    public static void prepare() {
        prepare(true);                                    ①
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {                 ②
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));        ③
    }
①:无参情况下调用prepare(true),形参置true表示允许退出。
②: sThreadLocal 会先去获取本地的数据,如果能获取到说明已经prepare过,则抛出异常。
③:设置sThreadLocal数据

sThreadLocal是ThreadLocal类型(static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();)
ThreadLocal: 线程本地存储区,每个线程都有自己的私有本地存储区域,不同的线程之间彼此不能访问对方的存储区。

接下来看下刚保存的TLS区域的Looper对象:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);           ①
        mThread = Thread.currentThread();                 ②
    }
①:创建一个消息队列
②:获取当前线程对象

这里为该线程创建了一个消息队列MessageQueue的构造函数中调用的hal层的本地方法:

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

这个流程的分析先到这。

2.2 Looper.loop()

public static void loop() {
        final Looper me = myLooper();                      ①
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block     ②
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);               ③

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();                        ④
        }
    }
①: 获取TLS中存储的Looper对象
②: 获取消息,没有消息的时候会阻塞(详见3.1)
③: 分发消息(详见4.1)
④: 回收消息到消息池(详见5.1)

三. MesageQueue

3.1 next()

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);                                   ①

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());                       ②
                }
                if (msg != null) {
                    if (now < msg.when) {                                                 ③
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;                                           ④
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {                                                          ⑤
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {                 ⑥
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {                                       ⑦
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();                                             ⑧
                } catch (Throwable t) {
                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;                                                  ⑨

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
①: 调用本地epoll方法, 当没有消息时会阻塞在这,阻塞时间为nextPollTimeoutMillis(详见6.1.1)
②: 查找消息队列中的异步消息(详见4.2)
③: 如果当前时间小于异步消息的触发时间,则设置下一轮poll的超时时间(相当于休眠时间),否则返回将要执行的异步消息.
④: 没有异步消息,下轮poll则无限等待,直到新的消息来临
⑤: 检测下退出标志
⑥: 如果消息队列未空或是第一个msg(消息刚放进队列且未达到触发时间),则执行空闲的handler
⑦: IdleHandler一个临时存放数组对象(下面可以看到一个列表转数组的方法被调用)
⑧: 运行空闲的handler(只有第一次循环时会运行idle handle)
⑨: 重置idle handler计数,防止下次运行

往往在第一次进入next函数循环时,在nativePollOnce阻塞之后,都会执行idle handle函数.
获取到异步消息,立马把该消息返回给上一层,否则继续循环等待新的消息产生.

3.2 enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {                                                             ①
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {                                                                  ②
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w("MessageQueue", e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {                                    ③
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {                                         ④
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);                                                             ⑤
        }
    }
    return true;
    }

①: 判断该消息是否有handler,每个msg必须有个对应的handler;
②: 判断该消息是否已经使用;
③: 判断是否有已经准备好的消息(表头消息)或当前发送消息的延时时间为0或next ready msg延时时间大于当前消息延时时间则将当前消息变为新的表头.;

  根据判断当前阻塞标志,来觉得是否需要唤醒;  

④: 根据时间将消息插入到消息对列中;
⑤: 上文分析在next()方法中会被阻塞,在这里就可以唤醒阻塞(详见6.1.2);

四. Handler

4.1 消息分发

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);                                                          ①
        } else {
            if (mCallback != null) {                                                      ②
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);                                                           ③
        }
    }
①: 如果该msg设置了回调函数,则直接调用回调方法message.callback.run();
②: 当handler设置了回调函数,则回调方法mCallback.handleMessage(msg);
③: 调用handler自身的方法handleMessage,该方法默认为空,一般通过子类覆盖来完成具体的逻辑;

我们Demo程序中,是使用第二种方法,设置回调来实现具体的逻辑,分发消息的本意是响应消息的对应的执行方法.

4.2 消息发送

clipboard.png

可以看到调用sendMessage方法后,最终调用的是enqueueMessage方法.

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

可以看到发送消息时都有一个时间参数选择,该参数就是我们前面分析的延时触发时间(相对时间).

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;                                                        ①
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;                                                                  ②
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
①: 判断handler创建时,传进来的消息对列是否为空(详见4.3)
②: 消息的target为该对象本身,handler类型

这里有对发生的消息进行异步标志设置,通过判断mAsynchronous标志,该标志是在创建handler时初始化的(详见4.3);
Handler.enqueueMessage方法调用的是MessageQueue.enqueueMessage方法(详见3.2);

4.3 创建Handler

4.3.1 无参构造

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

无参构造方式比起我们Demo中的方式,它自己回调用 Looper.myLooper()静态方法获取looper;

4.3.2 有参构造

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);                                                    ①
    }
    
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
①: 调用有参构造函数创建handler且异步标志置false说明该handler发送的消息都为同步消息.

Demo中的handler就是使用该方式创建,自己传入looper参数.

五. Message

5.1 recycle()

    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;                                                              ①
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {                                              ②
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
①: 将该消息标志置为使用中并清除其他参数为default
②: 将该消息加入消息池,当消息池未满时

将消息回收到消息池都是将消息加入到消息池的链表表头.

5.2 obtain()

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;                                                        ①
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();                                                             ②
    }
①: 从消息池表头拿出一个消息
②: 如果消息池为空则创建一个消息

可以看出每次从消息池取出消息都是从链表的表头取出,再对消息的计数做减法.

六. HAL层

native层本身也有一套完整的消息机制,用于处理native的消息;
在整个消息机制中,MessageQueue是连接java层和native层的纽带;

6.1 MessageQueue

文件

android_os_MessageQueue.c
static JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
    { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
    { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
    { "nativeIsIdling", "(J)Z", (void*)android_os_MessageQueue_nativeIsIdling }
};

以上可以看出上层调用nativePollOnce方法实质是调用HAL层的android_os_MessageQueue_nativePollOnce方法

6.1.1 nativePollOnce

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
    mInCallback = true;
    mLooper->pollOnce(timeoutMillis);
    mInCallback = false;
    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

通过源码可以看出消息队列中的pollOnce实质是调用的looper中的pollOnce方法(详见6.2.1)

6.1.2 nativeWake

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    return nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

通过源码可以看出消息队列中的wake实质是调用的looper中的wake方法(详见6.2.4)

6.1.3 nativeInit

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}


NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

可以看到hal层和java层中创建looper的时序几乎是一样的,先创建一个消息对列,再创建一个looper(Looper的构造详见6.2.3);

6.1.4 nativeIsIdling

static jboolean android_os_MessageQueue_nativeIsIdling(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    return nativeMessageQueue->getLooper()->isIdling();
}

bool Looper::isIdling() const {
    return mIdling;
}

还是调用looper中的方法,来看看这个标志具体表示什么状态:

    // We are about to idle.
    mIdling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mIdling = false;

以上代码片为Looper::pollInner中的一段,在wait时是空闲,当有数据来临时是非空闲的;
以前也用过这样的方法来判断线程是否在使用,想不到在这里也看到了这种方法;

6.1.5 nativeDestroy

static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->decStrong(env);
}

6.2 Looper

Looper.cpp: system/core/lib/libutils

6.2.1 Looper::pollOnce

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                        this, ident, fd, events, data);
#endif
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        result = pollInner(timeoutMillis);
    }
}

Looper::pollOnce是通过调用Looper::pollInner方法实现;

6.2.2 Looper::pollInner

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif

    // Adjust the timeout based on when the next message is due.
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

    // Poll.
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mIdling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);    ①

    // No longer idling.
    mIdling = false;

    // Acquire lock.
    mLock.lock();

    // Check for poll error.
    if (eventCount < 0) {                                                                   ②
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {                                                                  ③
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = POLL_TIMEOUT;
        goto Done;
    }

    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif

    /* 处理epoll后的所有事件 */
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {                                                        ④
            if (epollEvents & EPOLLIN) {
                    awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;

    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();

#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
                        this, handler.get(), message.what);
#endif
                handler->handleMessage(message);
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            result = POLL_CALLBACK;
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}
①: 等待mEpollFd有事件产生,等待时间为timeoutMilli;
当上层发消息时且判断需要唤醒,则会往管道的读端写入数据用于唤醒(详见6.2.3);
②: 检测poll是否出错;
③: 检测poll是否超时;
④: 如果是因为往管道读端写入数据被唤醒,则都去并清空管道中的数据;

6.2.3 Looper::Looper()

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    int result = pipe(wakeFds);                                                              ①
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);                                    ②
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
            errno);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
            errno);

    mIdling = false;

    // Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;                                                             ③
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);              ④
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
}
①: 创建一个无名管道, wakeFds[0]:读文件描述符, wakeFds[1]: 写文件描述符;
②: 更改为无阻塞方式;
③: EPOLLIN:连接到达,有数据来临;
④: 监测管道读端是否有数据来临;

6.2.4 Looper::wake()

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite != 1) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

唤醒只是向管道的写端写入一个字节数据,epoll_wait则会得到返回;

总结

在这里做个总结针对java层(因为native层的消息机制未进行详细分析不过估计和java层的流程差不多);
当调用j静态方法Looper.prepare()初始化后,再调用Looper.loop()方法进行消息循环处理;
Looper.loop()方法中调用MesageQueue.next()方法检索新消息,没有则阻塞,有则将消息插入消息链表头后立即返回;
阻塞方式是调用本地的nativePollOnce()方法实现,其原理是利用epoll管道文件描述符实现;
Looper.loop()调用dispatchMessage方法实现消息的分发处理;
发送一个消息的实质是调用个MessageQueue.enqueueMessage()方法往消息链表中插入一个消息,插入位置的条件为延时时间;
然后再调用一个本地方法nativeWake对前面阻塞的进行唤醒,实质是往管道中写入一个字节数据;

参考

http://gityuan.com/2015/12/26...

查看原文

赞 6 收藏 4 评论 0

Sourcelink 关注了专栏 · 2018-11-05

《Android 10 系统开发源码钻研录》

One blog for recorded and shared, Android system development. Framework, Mechanism, optimization, Stability, Views, Process, Algorith, etc...

关注 3031