学习分享
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021最新上万页的大厂面试真题
七大模块学习资料:如NDK模块开发、Android框架体系架构…
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
t->priority = target_proc->default_priority;
}
5.在目标进程
(也就是服务端
)的缓冲区分配空间,复制用户进程
的数据到内核
t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
tr->offsets_size, extra_buffers_size,
!reply && (t->flags & TF_ONE_WAY));
//…
if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
tr->data.ptr.buffer, tr->data_size)) {
//…
}
if (copy_from_user(offp, (const void __user *)(uintptr_t)
tr->data.ptr.offsets, tr->offsets_size)) {
//…
}
- 这里复制的数据是
Binder调用
的参数数据,也是需要传递给服务进程
的数据,因此需要缓冲区。这个缓冲区是在目标进程
的大的缓冲区中分配的
- 处理传输中的
Binder对象
off_end = (void *)off_start + tr->offsets_size;
sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));
sg_buf_end = sg_bufp + extra_buffers_size;
off_min = 0;
for (; offp < off_end; offp++) {
//…
}
- 通过参数传递的
Binder对象
需要进行转换
,这里的for循环
就是进行转换操作,内容较多,会在下面处理传递的Binder对象
章节进行讲解
- 将本次调用的
binder_transaction
结构体链接到线程的binder_stack
列表中
if (!(t->flags & TF_ONE_WAY)) {
//…
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
// 书中下面注释部分的代码已经整合到 binder_proc_transaction 函数中了
// 首先,将结构 binder_transaction中的binder_work放到目标目标进程或线程的todo队列
// 然后,创建一个新的binder_work的结构体,并将它放到发送线程的todo队列
if (!binder_proc_transaction(t, target_proc, target_thread)) {
//… 失败处理
}
}
-
binder_transaction
结构中包含了一个binder_work
的结构体,因此它可以被放到todo队列
中 -
本地调用时
binder_work
的type
被设置为BINDER_WORK_TRANSACTION
后,插入了目标进程
或目标线程
的todo队列
中 -
为了使客户进程能收到回复消息,这里也会创建一个新的
binder_work
的结构体,并把它的type
设置成了BINDER_WORK_TRANSAXTION_COMPLETET
,并将它插入到当前线程的todo队列
中
前面介绍过,在binder_thread_write
函数执行完后,还会去判断是否需要执行binder_thread_read
函数:
-
由于调用端执行完
BC_TRANSACTION
后,会立刻执行ioctl的读指令 -
所以,在调用操作上,
binder_thread_read
函数算是紧接着binder_thread_write
函数后执行的
刚才新建的binder_work
的结构体已经插入todo队列
了,我们看下binder_thread_read
函数会进行哪些和todo队列
相关的操作:
while (1) {
//…
// 获得todo队列,获取失败则goto retry
if (!binder_worklist_empty_ilocked(&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(&proc->todo) &&
wait_for_proc_work)
list = &proc->todo;
else {
binder_inner_proc_unlock(proc);
/* no data added */
if (ptr - buffer == 4 && !thread->looper_need_return)
goto retry;
break;
}
//取出todo队列中的元素
w = binder_dequeue_work_head_ilocked(list);
//…
switch (w->type) {
//…省略大量case语句
case BINDER_WORK_TRANSACTION_COMPLETE: {
binder_inner_proc_unlock(proc);
cmd = BR_TRANSACTION_COMPLETE;
//把返回消息通过put_user放到用户空间的指针中
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
binder_stat_br(proc, thread, cmd);
kfree(w);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
} break;
//…省略大量case语句
}
//…
}
binder_thread_read
函数所做的处理就是把回复消息BR_TRANSACTION_COMPLETE
复制到用户空间
这样客户进程就可以收到回复消息了
- 到这里,
客户进程
的调用结束了。但是,Binder调用才完成一半,接下来,看看服务进程是如何调用数据的。
服务进程
的调用流程
服务进程
至少有一个线程会在ioctl上等待调用的到来。服务进
程调用ioctl时传递的是读数据的请求,所以最后调用的也是binder_thread_read
函数,我们看下binder_thread_read
函数完整的处理流程:
- 如果保存返回结果的缓冲区中还没有数据,先写入
BR_NOOP
消息:
if (*consumed == 0) {
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}
- 进入循环处理所有
todo队列
中的工作
while (1) {
//…
}
- 读取线程或进程
todo队列
中需要完成的工作
struct binder_work *w = NULL;
//…
if (!binder_worklist_empty_ilocked(&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(&proc->todo) &&
wait_for_proc_work)
list = &proc->todo;
//…
w = binder_dequeue_work_head_ilocked(list);
- 用
switch
语句处理所有类型的工作
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
binder_inner_proc_unlock(proc);
t = container_of(w, struct binder_transaction, work);
} break;
}
-
经过
客户进程的调用流程
后,此时的服务进程
中已经存在一个类型为BINDER_WORK_TRANSACTION
的工作需要处理 -
这里只是取出了和
binder_work
关联的binder_transaction
结构体指针 -
并保存到变量
t
中
- 调整线程的优先级
if (!t)
continue;
if (t->buffer->target_node) {
struct binder_node *target_node = t->buffer->target_node;
struct binder_priority node_prio;
tr.target.ptr = target_node->ptr;
tr.cookie = target_node->cookie;
node_prio.sched_policy = target_node->sched_policy;
node_prio.prio = target_node->min_priority;
binder_transaction_priority(current, t, node_prio,
target_node->inherit_rt);
cmd = BR_TRANSACTION;
}
- 这里复制的数据是
Binder调用
的参数数据,也是需要传递给服务进程
的数据,因此需要缓冲区。这个缓冲区是在目标进程
的大的缓冲区中分配的
- 处理传输中的
Binder对象
off_end = (void *)off_start + tr->offsets_size;
sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));
sg_buf_end = sg_bufp + extra_buffers_size;
off_min = 0;
for (; offp < off_end; offp++) {
//…
}
- 通过参数传递的
Binder对象
需要进行转换
,这里的for循环
就是进行转换操作,内容较多,会在下面处理传递的Binder对象
章节进行讲解
- 将本次调用的
binder_transaction
结构体链接到线程的binder_stack
列表中
if (!(t->flags & TF_ONE_WAY)) {
//…
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
// 书中下面注释部分的代码已经整合到 binder_proc_transaction 函数中了
// 首先,将结构 binder_transaction中的binder_work放到目标目标进程或线程的todo队列
// 然后,创建一个新的binder_work的结构体,并将它放到发送线程的todo队列
if (!binder_proc_transaction(t, target_proc, target_thread)) {
//… 失败处理
}
}
-
binder_transaction
结构中包含了一个binder_work
的结构体,因此它可以被放到todo队列
中 -
本地调用时
binder_work
的type
被设置为BINDER_WORK_TRANSACTION
后,插入了目标进程
或目标线程
的todo队列
中 -
为了使
客户进程
能收到回复消息,这里也会创建一个新的binder_work
的结构体,并把它的type
设置成了BINDER_WORK_TRANSAXTION_COMPLETET
,并将它插入到当前线程的todo队列
中
前面介绍过,在binder_thread_write
函数执行完后,还会去判断是否需要执行binder_thread_read
函数:
-
由于调用端执行完
BC_TRANSACTION
后,会立刻执行ioctl
的读
指令 -
所以,在调用操作上,
binder_thread_read
函数算是紧接着binder_thread_write
函数后执行的
刚才新建的binder_work
的结构体已经插入todo队列
了,我们看下binder_thread_read
函数会进行哪些和todo队列
相关的操作:
while (1) {
//…
// 获得todo队列,获取失败则goto retry
if (!binder_worklist_empty_ilocked(&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(&proc->todo) &&
wait_for_proc_work)
list = &proc->todo;
else {
binder_inner_proc_unlock(proc);
/* no data added */
if (ptr - buffer == 4 && !thread->looper_need_return)
goto retry;
break;
}
//取出todo队列中的元素
w = binder_dequeue_work_head_ilocked(list);
//…
switch (w->type) {
//…省略大量case语句
case BINDER_WORK_TRANSACTION_COMPLETE: {
binder_inner_proc_unlock(proc);
cmd = BR_TRANSACTION_COMPLETE;
//把返回消息通过put_user放到用户空间的指针中
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
binder_stat_br(proc, thread, cmd);
kfree(w);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
} break;
//…省略大量case语句
}
//…
}
-
binder_thread_read
函数所做的处理就是把回复消息BR_TRANSACTION_COMPLETE
复制到用户空间 -
这样
客户进程
就可以收到回复消息了
到这里,客户进程
的调用结束了。但是,Binder调用
才完成一半,接下来,看看服务进程
是如何调用数据的。
服务进程
的调用流程
服务进程
至少有一个线程会在ioctl上等待调用的到来。服务进程
调用ioctl时传递的是读数据的请求,所以最后调用的也是binder_thread_read
函数,我们看下binder_thread_read
函数完整的处理流程:
- 如果保存返回结果的缓冲区中还没有数据,先写入
BR_NOOP
消息:
if (*consumed == 0) {
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}
- 进入循环处理所有
todo队列
中的工作
while (1) {
//…
}
- 读取线程或进程
todo队列
中需要完成的工作
struct binder_work *w = NULL;
//…
if (!binder_worklist_empty_ilocked(&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(&proc->todo) &&
wait_for_proc_work)
list = &proc->todo;
//…
w = binder_dequeue_work_head_ilocked(list);
- 用
switch语句
处理所有类型的工作
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
binder_inner_proc_unlock(proc);
t = container_of(w, struct binder_transaction, work);
} break;
}
-
经过
客户进程的调用流程
后,此时的服务进程
中已经存在一个类型为BINDER_WORK_TRANSACTION
的工作需要处理 -
这里只是取出了和
binder_work
关联的binder_transaction
结构体指针 -
并保存到变量
t
中
- 调整线程的优先级
if (!t)
continue;
if (t->buffer->target_node) {
struct binder_node *target_node = t->buffer->target_node;
struct binder_priority node_prio;
tr.target.ptr = target_node->ptr;
tr.cookie = target_node->cookie;
node_prio.sched_policy = target_node->sched_policy;
node_prio.prio = target_node->min_priority;
binder_transaction_priority(current, t, node_prio,
target_node->inherit_rt);
cmd = BR_TRANSACTION;
}
-
如果t为NULL,继续循环
-
否则,开始准备返回的消息
BR_TRANSACTION
-
同时,设置线程的优先级
-
如果
调用线程
的优先级低于当前线程
指定的最低优先级
,则把当前线程
的优先级设为调用线程
的优先级 -
否则,把
当前线程
设为指定的最低优先级
-
这意味着
Binder线程
会以尽量低的优先级运行
- 准备返回的数据
tr.code = t->code;
tr.flags = t->flags;
tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);
//…
// 让tr中的data指针指向内核中保存的数据缓冲区
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 +
binder_alloc_get_user_buffer_offset(&proc->alloc));
tr.data.ptr.offsets = tr.data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));
// 把 BR_TRANSACTION 消息复制到用户空间
if (put_user(cmd, (uint32_t __user *)ptr)) {
//…
return -EFAULT;
}
ptr += sizeof(uint32_t);
if (copy_to_user(ptr, &tr, sizeof(tr))) {
//把结构体tr数据复制到用户空间
//…
return -EFAULT;
}
ptr += sizeof(tr);
//…
break;//跳出while循环
这一段代码都是为消息BR_TRANSACTION
准备返回数据,要注意的是:
-
调用
copy_to_user
复制到用户空间
的只是结构体tr
的数据 -
服务进程
得到这个结构体之后,会直接读取它里面的data指针
的数据 -
数据准备完毕后,使用
break语句
跳出while循环
- 启动新线程
if (proc->requested_threads == 0 &&
list_empty(&thread->proc->waiting_threads) &&
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++;
//…
if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
return -EFAULT;
}
当前线程要执行Binder调用
,新来的调用也需要线程来处理,因此:
-
函数结束前会检查进程中可用的线程数
-
如果需要创建新线程,则在返回的
buffer数据
中增加BR_SPAWN_LOOPER
消息,服务进程
收到这个消息会启动新的线程
完整的Binder调用过程还需要把回复消息传递给客户进程,这个过程使用的函数还是前面的这些,暂时不分析了。消化一下先
前面介绍了Binder对象
传递的原理和用户层的实现。(原理上整个人还是晕晕的),我们来看下Binder驱动
如何实现Binder对象
的传递的。
-
Binder驱动
中,代表每个进程的结构binde_proc
中有两个字段:nodes
和refs_by_node
-
这两个字段各指向两颗红黑树的头
-
nodes
:指向的是Binder节点对象表
,储存本进程中Binder实体对象
相关的数据 -
refs_by_node
:指向的是Binder引用对象表
,存储本进程的Binder引用对象
的数据以及对应的实体对象的节点指针
来个抽象点的图:
Binder驱动
中处理对象转换的代码位于函数binder_transaction
中
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_binder(fp, t, thread);
} break;
static int binder_translate_binder(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
struct binder_node *node;
struct binder_proc *proc = thread->proc;
struct binder_proc *target_proc = t->to_proc;
struct binder_ref_data rdata;
int ret = 0;
//根据binder值查找Binder对象表中的节点
node = binder_get_node(proc, fp->binder);
if (!node) { //没有则新建一个节点
node = binder_new_node(proc, fp);
if (!node)
return -ENOMEM;
}
//…
if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
ret = -EPERM;
goto done;
}
// 在接收端进程中寻找节点的引用,找不到会创建一个新的引用
ret = binder_inc_ref_for_node(target_proc, node,
fp->hdr.type == BINDER_TYPE_BINDER,
&thread->todo, &rdata);
if (ret)
goto done;
// 将传递的binder数据结构的type的值改为 BINDER_TYPE_HANDLE
if (fp->hdr.type == BINDER_TYPE_BINDER)
fp->hdr.type = BINDER_TYPE_HANDLE;
else
fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
fp->binder = 0;
// rdata.desc存放的是节点引用表中的序号,赋值给handle
fp->handle = rdata.desc;
fp->cookie = 0;
//…
}
上面的流程是
-
通过
binder_get_node
函数在发送进程的Binder对象节点表
中查找节点 -
查找是通过比较数据中的
binder字段
和节点中对应的字段进行的 -
binder字段
中存放的是Binder对象
的弱引用指针 -
如果没找到
-
新建节点
-
把
binder字段
、cookie字段
的值保存到新节点 -
通过
binder_inc_ref_for_node
函数在目标进程查找Binder节点
的引用
。 -
没找到会新建一个
-
引用
指的是节点引用表
中的refs_by_node
节点,包含指向Binder节点
的指针 -
把数据的
type
字段的值改为BINDER_TYPE_HANDLE
或BINDER_TYPE_WEAK_HANDLE
-
把
handle
字段的值设为在节点引用表
的序号,这也是Binder引用对象
中handle值
的来历
下面再看看如何处理类型BINDER_TYPE_HANDLE
或BINDER_TYPE_WEAK_HANDLE
的
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct flat_binder_object *fp;
fp = to_flat_binder_object(hdr);
ret = binder_translate_handle(fp, t, thread);
} break;
static int binder_translate_handle(struct flat_binder_object *fp,
struct binder_transaction *t,
struct binder_thread *thread)
{
//…
// 通过handle值查找节点引用
node = binder_get_node_from_ref(proc, fp->handle,
fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata);
if (!node) {
//…
return -EINVAL;
}
//…
if (node->proc == target_proc) {
// 如果目标进程就是Binder对象的进程,开始转换
if (fp->hdr.type == BINDER_TYPE_HANDLE)
fp->hdr.type = BINDER_TYPE_BINDER;
else
fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
fp->binder = node->ptr;
fp->cookie = node->cookie;
if (node->proc)
binder_inner_proc_lock(node->proc);
binder_inc_node_nilocked(node,
fp->hdr.type == BINDER_TYPE_BINDER,
0, NULL);
//…
} else {
//…
// 如果不是,则在目标进程新建一个Binder节点的引用
ret = binder_inc_ref_for_node(target_proc, node,
fp->hdr.type == BINDER_TYPE_HANDLE,
NULL, &dest_rdata);
//…
fp->handle = dest_rdata.desc;
fp->cookie = 0;
trace_binder_transaction_ref_to_ref(t, node, &src_rdata,
//…
}
}
这部分代码的流程是:
-
通过传输数据的
handle字段
在发送进程的节点引用表
中查找 -
正常情况下是可以找到,不能就错误返回
-
如果目标进程就是Binder对象所在的进程
-
开始进行转换,把数据的
type字段
转为BINDER_TYPE_BINDER
或BINDER_TYPE_WEAK_BINDER
BINDER_WORK_TRANSACTION -
把
binder
和cookie
字段设置为节点中保存的值 -
如果目标进程不是Binder对象所在的进程
-
在目标对象中建立一个节点对象的引用
到这里呢,Binder原理部分就差不多了,已经了解了包括:
-
客户端
调用
、接收
消息的过程 -
服务端
监听
、回复
消息的过 -
驱动
传输数据
、调度线程
的操作
现在,我们再来看最后的一小部分:ServiceManager
的作用
=================================================================================
关于ServiceManager
,先简单描述:
-
ServiceManager
是Binder架构
中用来解析Binder名称
的模块 -
ServiceManager
本身就是一个Binder服务
-
ServiceManager
并没有使用libbinder
来构建Binder服务
-
2020-09-04 同步了下项目代码,发现也替换成
libbinder
那一套了。。。。。 -
不确定是不是更换仓储了
-
要是这样就没意思了,简易版本对于加深
binder
理解还是很有帮助的 -
我们暂且按照老的版本来看下吧
-
ServiceManager
自己实现了一个简单的binder框架
来直接和驱动通信。。。
ServiceManager
源码路径在:frameworks/native/cmds/servicemanager
,主要包含两个文件:
-
binder.c
:用来实现简单的Binder通信功能 -
简单版本的
binder协议
实现 -
直接的
ioctl
操作与binder驱动
通信 -
不是本节的重点,感兴趣可以参照源码来学习啦
-
service_manager.c
:用来实现ServiceManager
的业务逻辑 -
重点是了解
ServiceManager
如何响应Binder服务
的注册和查询的 -
这么重要的服务要在什么时间启动呢?
-
这部分是在
system/core/rootdir/init.rc
中配置
on post-fs
…
start essential services
start logd
start servicemanager
start hwservicemanager
start vndservicemanager
-
hwservicemanager
用来支持HIDL
-
vndservicemanager
第三方厂商使用,应该是从Treble架构
中出现的
从ServiceManager
的main
函数开始:
int main(int argc, char** argv)
{
struct binder_state *bs;
union selinux_callback cb;
char *driver;
if (argc > 1) {
driver = argv[1];
} else {
driver = “/dev/binder”;
}
bs = binder_open(driver, 128*1024);
if (!bs) {
//…
// 省略一些宏判断
return -1;
}
if (binder_become_context_manager(bs)) {
ALOGE(“cannot become context manager (%s)\n”, strerror(errno));
return -1;
}
// 设置selinux callback
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
cb.func_log = selinux_log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
//…
// 省略一些宏判断,都是为了获取sehandle(检查SELinux权限)
sehandle = selinux_android_service_context_handle();
selinux_status_open(true);
if (sehandle == NULL) {
ALOGE(“SELinux: Failed to acquire sehandle. Aborting.\n”);
abort();
}
if (getcon(&service_manager_context) != 0) {
ALOGE(“SELinux: Failed to acquire service_manager context. Aborting.\n”);
abort();
}
binder_loop(bs, svcmgr_handler);
return 0;
}
main
函数的流程如下:
-
首先调用
binder_open
来打开binder设备
和初始化系统 -
同时创建一块
128x1024
大小的内存空间 -
接着调用
binder_become_context_manager
把本进程设置为Binder框架
的管理进程 -
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
发到了驱动 -
最后执行
binder_loop(bs, svcmgr_handler)
,简单处理后,循环等待消息
我们要说一说binder_loop(bs, svcmgr_handler)
,小弟C语言
不熟,感觉这个操作很SAO
:
binder_loop
的函数定义是:
void binder_loop(struct binder_state *bs, binder_handler func)
- 第二个参数传入的是一个
binder_handler
类型
- 再来看下
binder_handler
类型的定义:
typedef int (*binder_handler)(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply);
-
这是定义了一个
函数指针类型
-
返回值是
int
- 再来看下
binder_loop(bs, svcmgr_handler)
中的svcmgr_handler
函数的声明
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
//…
// 省略一些switch语句,稍后详解
return 0;
}
-
传入的指针是
svcmgr_handler
函数指针 -
应该会自动转型为
binder_handler
指针类型 -
毕竟函数参数、返回值都是一样的
-
这部分是我觉得最神奇的地方。。。。。
-
特意写了个C代码试了下,
-
如果定义的函数的
参数
、返回值
与typedef
定义的函数指针
不一致的话,强转编译会失败 -
不过这种操作感觉还是很不正经。。(还是Java更严谨一些)
- 参数理解的差不多了,我们仔细看下
binder_loop
的实现,这两个参数传进去干了啥。
PS:简化版就是更容易理解些。。。。。。
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;
}
// 读取到数据后
// 调用 binder_parse 进行数据解析
// 顺便把 binder_handler 函数指针也传递进去
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
// 异常情况,退出循环
if (res == 0 || res < 0) {
//…
break;
}
}
}
-
有注释,很很简洁的逻辑
-
最后执行到了
binder_parse
函数
- 我们继续跟进
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;
switch(cmd) {
//…
// 由于binder_handler 在这里会被执行到
// 所以我们先重点看这个,当Binder调用过来时
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;
}
binder_dump_txn(txn);
// 如果函数指针存在
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);
//执行 binder_handler 函数指针指向的函数
res = func(bs, txn, &msg, &reply);
if (txn->flags & TF_ONE_WAY) {
// 这种情况不会返回结果
binder_free_buffer(bs, txn->data.ptr.buffer);
} else {
// 返回结果数据
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
}
}
ptr += sizeof(*txn);
break;
}
//…
}
return r;
}
-
还是看注释哈,代码很简洁,注释很详细,哈哈哈
-
到这里,我们可以看出来,真正处理
调用服务
的是binder_handler
这个函数指针啊 -
也就是说
远程调用
的处理逻辑在svcmgr_handler
这个函数呀 -
666!
- 我们来看下
svcmgr_handler
函数
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
//…
// 如果请求的目标服务不是ServiceManager,直接返回
if (txn->target.ptr != BINDER_SERVICE_MANAGER)
return -1;
// 如果请求消息内容只是简单的测试通路,不需要继续执行,直接返回 0
if (txn->code == PING_TRANSACTION)
return 0;
// 检查收到的消息 id 串
strict_policy = bio_get_uint32(msg);
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
//…
// 检查SELinux 的权限
if (sehandle && selinux_status_updated() > 0) {
struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
if (tmp_sehandle) {
selabel_close(sehandle);
sehandle = tmp_sehandle;
}
}
switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
// 处理查询或者获取服务的指令
//…
break;
case SVC_MGR_ADD_SERVICE:
// 处理注册服务的的指令
//…
break;
case SVC_MGR_LIST_SERVICES: {
//…
// 处理获取服务列表的指令
}
default:
ALOGE(“unknown code %d\n”, txn->code);
return -1;
}
//发送返回消息
bio_put_uint32(reply, 0);
return 0;
}
ServiceManager
的架构非常简单高效,只有一个循环来和binder驱动
进行通信
从svcmgr_handler
函数中可以看到,ServiceManager
提供了三种服务功能:
-
注册
Binder服务
-
查询
Binder服务
-
获取
Binder服务列表
在case SVC_MGR_ADD_SERVICE
中实现的注册Binder服务
功能,具体的实现函数是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, uint32_t dumpsys_priority, pid_t spid) {
struct svcinfo *si;
// 一些基础的信息判断
if (!handle || (len == 0) || (len > 127))
return -1;
// 检查调用进程是否有权限注册服务
if (!svc_can_register(s, len, spid, uid)) {
//…省略log打印
return -1;
}
// 查看要注册的服务是否已经存在
si = find_svc(s, len);
if (si) {
// 如果存在,先把以前的Binder对象的引用计数减一
if (si->handle) {
svcinfo_death(bs, si);
}
// 把原先节点中的handle替换成新的handle
si->handle = handle;
} else {
// 服务不存在,则生成新的列表项,初始化后加入列表
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
if (!si) {
// 内存申请失败,直接退出
return -1;
}
// 一些初始化操作
si->handle = handle;
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
own code %d\n", txn->code);
return -1;
}
//发送返回消息
bio_put_uint32(reply, 0);
return 0;
}
ServiceManager
的架构非常简单高效,只有一个循环来和binder驱动
进行通信
从svcmgr_handler
函数中可以看到,ServiceManager
提供了三种服务功能:
-
注册
Binder服务
-
查询
Binder服务
-
获取
Binder服务列表
在case SVC_MGR_ADD_SERVICE
中实现的注册Binder服务
功能,具体的实现函数是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, uint32_t dumpsys_priority, pid_t spid) {
struct svcinfo *si;
// 一些基础的信息判断
if (!handle || (len == 0) || (len > 127))
return -1;
// 检查调用进程是否有权限注册服务
if (!svc_can_register(s, len, spid, uid)) {
//…省略log打印
return -1;
}
// 查看要注册的服务是否已经存在
si = find_svc(s, len);
if (si) {
// 如果存在,先把以前的Binder对象的引用计数减一
if (si->handle) {
svcinfo_death(bs, si);
}
// 把原先节点中的handle替换成新的handle
si->handle = handle;
} else {
// 服务不存在,则生成新的列表项,初始化后加入列表
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
if (!si) {
// 内存申请失败,直接退出
return -1;
}
// 一些初始化操作
si->handle = handle;
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter
[外链图片转存中…(img-jrTHZhOn-1715678580709)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!