引言
在博客Android Binder机制(3)本地服务注册过程这篇博客中我们详细讲解了本地服务的注册过程,除了一个地方之外,那就是IPCThreadState::waitForResponse()方法中的talkWithDriver(),而在talkWithDriver()中调用了binder_ioctl(),由于内容太多,所以专门写一篇博客进行分析。
实际上,不只是在服务注册过程中会调用到Binder Driver中的binder_ioctl(),在服务检索、服务使用阶段都会调用到binder_ioctl(),所以这篇博客将分这3个阶段详细讲解binder_ioctl()方法。
并且,这3个阶段均可按Binder数据的传递划分为6个阶段,如下所示:
本文中对于服务注册、服务检索、服务使用都分这6个阶段进行讲解。
另外,由于在博客Android Binder机制(2) ContextManager注册过程分析中详细分析过binder_open和binder_mmap()函数,这里就不再讲解了,不了解的小伙伴可以移步到那篇Blog中。
在开始之前,我们先补充一些基础知识。
0.1 ioctl的BINDER_WRITE_READ命令用来请求Binder Driver发送或接收IPC数据以及IPC应答数据
0.2 binder_ioctl()函数负责在两个进程间收发IPC数据以及IPC应答数据。因此,在分析binder_ioctl()函数时,必然要涉及到发送IPC数据和接收IPC数据的两个进程。
0.3 binder_write_read结构体的定义如下:
struct binder_write_read- write_buffer成员变量用于发送IPC数据或IPC应答数据。而read_buffer成员变量用来接收来自Binder Driver的数据,即Binder Driver在接收IPC数据或IPC应答数据后,将它们保存到read_buffer中,而后再传递到用户空间中。
- write_size与read_size分别用来指定write_buffer与read_buffer的数据大小
- write_consumed与read_consumed分别用来设定write_buffer与read_buffer中被处理的数据大小
- 在前面的学习中,我们知道IPC数据与IPC应答数据由Handle、RPC代码、RPC数据、Binder协议构成。其中Handle、RPC代码、RPC数据保存在名称为binder_transaction_data的结构体中。如下图所示:
如上图所示,binder_write_read的write_buffer指向含有Binder协议和binder_transaction_data的内存区域,在Binder Driver处理IPC数据时它们被作为一个处理单元。通常这种处理单元能被连续加载到write_buffer中,但为了说明的方便,只考虑有一个IPC数据的情形。同样,read_buffer指向由Binder协议与binder_transaction_data指向的内存区域
binder_write_read结构体中包含着用户空间中生成的IPC数据,Binder Driver也拥有一个相同的结构体。用户空间设置完binder_write_read结构体的数据后,调用ioctl()函数传递给Binder Driver,Binder Driver调用copy_from_user()函数将用户空间中的数据拷贝到自身的binder_write_read结构体中。相反,在传递IPC应答数据时,Binder Driver将调用copy_to_user()函数,将自身binder_write_read结构体中的数据拷贝到用户空间。
1.服务注册
1.1 过程概览
服务注册过程的示意图如下所示:
通过前面的博客我们知道,Context Manager先于其他所有Service Server进行,它最先使用Binder Driver,进入待机状态,之后等待Service Server的服务注册请求或者Client的服务检索请求。
上图所示过程如下:
1)Context Manager通过open()函数调用binder_open()函数,Binder Driver使用该函数为Context Manager生成并初始化binder_proc结构体;
2)Context Manager在内核空间中开辟一块Buffer,用于接收IPC数据。Context Manager通过mmap()函数调用Binder Driver的binder_mmap()函数。Binder Driver使用binder_mmap()函数在内核空间中分配一块用于接收IPC数据的Buffer,其大小为调用mmap()函数时指定的大小,并被保存到binder_buffer结构体中。而后binder_buffer变量被注册到binder_proc结构体中;
3)Context Manager通过ioctl()调用Binder Driver的binder_ioctl()函数。在binder_ioctl()函数中,Context Manager将处于待机状态,直到另一个进程(在服务注册阶段是指Service Server)向共发送IPC数据;
4)Service Server通过open()调用Binder Driver的binder_open()函数,Binder Driver为Service Server生成binder_proc结构体,并将其初始化;
5)Service Server通过mmap()调用Binder Driver的binder_mmap()函数.Binder Driver开辟一块用于接收IPC应答数据的Buffer,并将其保存到binder_buffer结构体中。而后Service Server生成IPC数据,用来调用Context Manager的服务注册函数;
6)为了注册服务,Service Server将IPC数据传递给Binder Driver。Binder Driver将根据IPC数据中的Handle查找到Context Manager的binder_node结构体,以及binder_proc结构体;
7)为Service Server要注册的服务生成binder_node结构体。Binder Driver将binder_node分别注册到Service Server的binder_proc结构体以及Context Manager的binder_proc结构体中;
8)Binder Driver将在6)中查找到的Context Manager的binder_proc结构体中查找注册在binder_buffer结构体中的Buffer,并传递IPC数据;
9)Binder Driver会记下IPC数据发送进程(Service Server)的binder_proc结构体,以便在Context Manager向Service Server发送应答数据时查找Serivce Server的binder_proc结构体;
10)Binder Driver让Service Server处于待机状态,并将Context Manager从待机状态中唤醒,传递IPC数据。从待机状态中唤醒的Context Manager经由Binder Driver接收来自Service Server的IPC数据。Context Manager根据接收的IPC数据注册指定的服务,而后生成IPC应答数据(通知服务注册完成),并将其传递给Binder Driver;
11)Binder Driver会在9)中的Service Server的binder_proc结构体中查找注册在binder_buffer结构体中的Buffer,并传送IPC应答数据,唤醒接收端的Service Server。Service Server被唤醒后,接收IPC应答数据,并根据接收的IPC应答数据进行相应处理。
1.2 传递Binder数据的阶段1
Context Manager调用binder_open()与binder_mmap()函数,初始化binder_proc结构体,创建用于接收IPC数据的binder_buffer结构体。
在用户空间中生成一块Buffer,将read_buffer注册到binder_write_read结构体中,而后调用ioctl()函数,此时,binder_write_read结构体的read_size指的是用户空间的Buffer的大小。此时binder_ioctl()中调用的主要代码如下:
binder_ioctl()binder_ioctl()函数的第二个参数cmd是ioctl()的命令,第三个参数arg是binder_write_read结构体,它是调用ioctl()函数时使用的参数。
- flip->private_data是在binder_open()函数中创建的binder_proc结构体
- 由于proc的线程红黑树中还没有新建相应的线程,故在binder_get_thread()函数中新建一个binder_thread结构体
- 分析ioctl()命令,前面已经说过,此时传递的命令是BINDER_WRITE_READ
- 调用copy_from_user()方法将Context Manager持有的用户空间的binder_write_read结构体复制到内核空间中。而后根据read_size与write_size变量判断是发送IPC数据,还是接收IPC应答数据。Context Manager在接收IPC数据时,将生成相应的read_buffer,并且read_size的值将大于0
- 由于read_size>0,故调用binder_thread_read()函数
1.2.1 binder_thread_read()分析
binder_thread_read()中调用到的代码如下所示:
binder_thread_read()Context Manager在binder_thread_read()函数中调用wait_event_interruptible_exclusive()函数。wait_event_interruptible_exclusive()函数通过当前进程的binder_proc结构体的wait变量将当前任务注册到待机队列中。而后将task_struct结构体内的state变量由TASK_RUNNING更改为TASK_INTERRUPTIBLE,再调用schedule()函数。当Context Manager的任务阙云太为TASK_INTERRUPTIBLE时,Context Manager就不再继续运行,它将进入待机状态,直至接收到IPC数据。
1.3 传递Binder数据的阶段2
这个阶段其实对应的是诸如MediaPlayerService::instantiate();这样的过程,只不过我们这里只从Binder Driver的角度考虑问题,所以只讨论ioctl()函数。
这个阶段的示意图如下:
Service Server也会生成binder_write_read结构体,包含write_buffer与read_buffer两个成员变量。其中,read_buffer变量用于接收IPC应答数据,write_buffer变量用于发送IPC数据。binder_write_read结构体内容如下图所示:
此时binder_ioctl()函数中执行的主要代码如下:
binder_ioctl()- Service Server调用binder_get_thread()函数生成binder_thread对象
- 调用copy_from_user()函数,将调用空间的binder_write_read结构体拷贝至内核空间中。为了传递IPC数据,Service Server保存了binder_write_read结构体的write_buffer数据,故write_size>0
- 由于write_size>0,故调用binder_thread_write()函数
binder_thread_write()函数的主要代码如下:
binder_thread_write()- buffer+*consumed指向的是当前write_buffer中要处理的数据;buffer+size则指向缓冲区的末尾
- 调用get_user()函数的目的是将IPC数据中的Binder协议拷贝至内核空间中。在服务注册时,所使用的Binder协议是BC_TRANSACTION
- 调用copy_from_user()函数,将IPC数据中不包含Binder协议的binder_transaction_data结构体复制到内核空间中
- 调用binder_transaction()函数,binder_transaction)函数使用binder_transaction_data结构体中的数据来执行Binder寻址、复制Binder IPC数据、生成及检索Binder节点等操作
binder_transaction()方法的主要代码如下:
binder_transaction()首先,由于tr->target.handle为0,故执行另一个分支,从而target_node=binder_context_mgr_node;即为Context Manager对应的binder_node,从而target_proc对应的是Context Manager的proc
由于target_thread为NULL,故执行else中的内容,从而将binder_proc数据结构中的todo变量赋给target_list,以指定要执行的任务;同时将target_proc的等待队列赋给target_wait;
变量t是一个指向binder_transactoin结构体的指针
IPC数据发送端与接收端通过struct binder_transaction来收发IPC数据,该结构体的定义如下:
struct binder_transactiont->from=thread;中的目的是查找发送IPC数据的进程,以便发送IPC应答数据,在收发Binder数据的第5个阶段中会用到
t->code=tr->code;目的是将IPC数据的RPC代码赋给binder_transaction结构体的code变量
binder_alloc_buf()函数的作用是分配一块空间,用于从接收端的IPC数据接收缓冲区中复制IPC数据。该接收缓冲空间由接收端在binder_mmap()函数中确定,binder_proc结构体的free_buffers指向该区域
copy_from_user(t->buffer->data,tr->data.ptr.buffer,tr->data_size);的作用是拷贝RPC数据到binder_buffer结构体的data变量中
至此,Binder数据发送端设置binder_transaction结构体的过程结束了。接下来,接收端将从待机状态中苏醒。并从该结构体中获取数据。
调用binder_get_node()函数,在注册至binder_proc结构体中的Binder节点中,确认当前的Binder节点是否已经存在。由于首次注册,故不存在,从而新建节点。
调用binder_new_node()函数不仅会创建新的binder_node,新生成的节点还会被注册到当前进程的binder_proc结构体中
调用binder_get_ref_for_node()函数,检查指定的Binder节点是否已经存在于指定的binder_proc结构体中。若存在,则返回Binder节点所在的binder_ref结构体,否则新建binder_ref结构体并将指定的Binder节点注册其中,而后将其返回。另外,binder_get_ref_for_node()函数也将binder_ref结构体注册到binder_proc中的refs_by_node中
接下来将BINDER_WORK_TRANSACTION赋值给t->work.type,这个值在第4个阶段会用到。当将type设置为BINDER_WORK_TRANSACTION时,Context Manager从binder_transaction结构体中获取Service Server传递而来的IPC数据,在第4阶段会详细讲解
调用wake_up_interruptible()函数,并使用之前赋值过的target_wait作为参数,将Context Manager从待机状态中唤醒。在接下来的进程调度中,Context Manager将执行第4阶段的相应任务。
1.4 传递Binder数据的阶段3
在传递Binder数据的第1个阶段中,Context Manager通过binder_write_read结构体的read_buffer进入到待机状态中。类似地,Service Server完成写入数据后,在binder_thread_read()中进入待机状态。Service Server将在收发Binder数据的第6个阶段中苏醒,接收IPC应答数据。
1.5 传递Binder数据的阶段4
Context Manager被唤醒后,将执行binder_thread_read()中接下来的动作。如下所示:
调用list_first_entry()函数,查找Service Server在前面代码中注册的binder_work结构体,并将查找到的binder_work结构体返回。list_first_entry()函数是container_of()函数的引用函数,container_of()函数将根据首个参数查找到包含该参数的结构体的首地址。
若binder_work结构体的type成员变量值为BINDER_WORK_TRANSACTION,则继续调用container_of()函数,查找持有binder_work结构体的binder_transaction结构体
之后,Context Manager通过Service Server创建的binder_transaction结构体获取IPC数据,并将IPC数据保存到binder_transaction_data结构体,以便将IPC数据传递到用户空间中,代码如下所示:
前面的代码都很好理解,关键是code_1处。内核空间接收Buffer的地址为t->buffer->data,而proc->user_buffer_offset则指用户空间的接收Buffer与内核空间的接收Buffer间的地址偏移。因此tr.data.ptr.buffer保存的是用户空间中接收Buffer的地址。binder_write_read结构体存在于用户空间中,read_buffer是binder_write_read结构体的成员变量,它可以引用保存在tr.data.ptr.buffer所指的用户空间中的IPC数据。在向接收端传递数据时,Binder IPC也采用相同的方法,将内核空间中的数据传递到用户空间中,而不需要调用copy_to_user()函数。通过这种方式,可以避免在内核空间与用户空间之间拷贝数据,从而提升IPC动作的效率。
接下来,就要将binder_transaction_data中的数据传递到用户空间中。从该阶段开始,数据将被保存到Context Manager的read_buffer中,相应的代码如下:
首先设置Binder协议为BR_TRANSACTION,准备处理Context Manager的IPC数据
*ptr指向binder_write_read结构体的read_buffer成员变量,在传递Binder数据的第1个阶段中,Context Manager在进入待机状态前调用ioctl()函数时,它作为一个参数被传入函数中
*ptr指向binder_write_read结构体的read_buffer成员变量
利用copy_to_user()将之前生成的binder_transaction_data结构体拷贝到read_buffer中
thread->transaction_stack=t;的目的是将当前的binder_transaction结构体注册到binder_thread结构体的transaction_stack中。binder_transaction结构体在传递Binder IPC数据的第5个阶段中用于查找IPC应答数据的接收端
1.6 传递Binder数据的阶段5
在此阶段中,Context Manager将处理接收到的IPC数据,而后向Service Server发送IPC数据,告知处理已经完成。该阶段 数据传递过程与传递Binder数据的第2个阶段类似,涉及到的Binder协议分别是BC_REPLY(Contxt Manager–>Binder Driver)和BR_REPLY(Binder Driver–>Context Manager).
首先由thread->transaction_stack获取前面刚刚讲解过的注册的binder_transaction结构体
之后由in_reply_to->from获取Service Server的binder_thread结构体
由binder_thread即可获取Service Server的binder_proc,从而可以获得Service Server的接收端Buffer.之后将IPC应答数据保存到binder_transaction中。然后Service Server从待机状态中苏醒,查找相应的结构体,将IPC应答数据传递到用户空间中。由于该吉祥平安福且贵与传递Binder数据的阶段2类似,故不再赘述。
1.7 传递Binder数据的阶段6
Service Server从待机状态中苏醒后,其行为动作与传递Binder数据第4个阶段类似,不同的是所使用的Binder协议为BR_REPLY.Service Server接收完IPC应答数据后将在用户空间中进行一系列与IPC应答数据相关的动作。
2.服务检索阶段
在服务检索阶段,调用binder_ioctl()函数,将服务客户端的IPC数据发送到Context Manager,并将Context Manager的IPC应答数据发送到客户端。
2.1 传递Binder数据的阶段1
该阶段类似注册服务时传递Binder数据的第1个阶段,Context Manager进入待机状态,等待接收IPC数据。
2.2 传递Binder数据的阶段2
在该阶段中Binder Driver的行为动作类似于注册服务时传递Binder数据的第2个阶段。但是发送IPC数据的进程不是Service Server,而是服务客户端。
服务客户端并不注册服务,所以在其RPC数据中不会存在flat_binder_object结构体。在服务客户端生成的IPC数据中包含Context Manager的Handle(0),RPC代码(服务检索函数)以及请求的服务名称。
需要注意的是,由于IPC数据中不包含flag_binder_object对象,所以不会创建binder_node对象。其他动作与服务注册传递Binder的阶段2是一样的。
2.3 传递Binder数据的阶段3
与Service Server类似,服务客户端既发送IPC数据,又接收IPC应答数据,它会使用binder_write_read结构体的write_buffer与read_buffer两个成员变量。同服务注册阶段的传递IPC数据的第3个阶段一样,服务客户端将进入待机状态中,等待接收IPC应答数据。
2.4 传递Bindder数据的阶段4
该阶段与服务注册阶段的阶段4类似。Context Manager接收IPC数据后,会在服务目录中生成binder_object结构体,该结构体中包含着与服务名称相对应的服务目录编号。并且,将结构体中的type变量设置为BINDER_TYPE_HANDLE,生成IPC应答数据。binder_object会被复制到Binder Driver的flat_binder_object中,用来查找Binder节点。在下一个阶段中,通过binder_object数据,将Binder节点传递给服务客户端。
2.5 传递Binder数据的阶段5
在该阶段中,服务客户端将获取所用服务的Binder节点。在服务注册阶段,flat_binder_object结构体的type变量为BINDER_TYPE_BINDER,继而生成Binder节点。但是在服务检索阶段,type为BINDER_TYPE_HANDLE,Binder节点被传递给服务客户端,主要代码如下:
调用binder_get_ref()函数,其第一个参数proc为Context Manager的binder_proc,第二个参数fp->handle是服务客户端要使用的服务编号。服务编号即binder_ref结构体中desc变量的值。将desc的值传入binder_get_ref()函数后,即可获得与服务编号相对应的binder_ref结构体,此结构体由Context Manager持有
调用binder_get_ref_for_node()函数,其中第一个参数target_proc是服务客户端的binder_proc结构体,第二个参数ref->node是ref对应的binder_node对象。在该函数内部,将为接收的binder_node结构体创建新的binder_ref结构体,并将其指向binder_node结构体
修改RPC数据中flat_binder_object结构体内的服务编号,将其设置为当前binder_ref结构体的desc编号。而后在下一个阶段中,将包含修改后的RPC数据的IPC应答数据发送到服务客户端
2.6 传递Binder数据的阶段6
该阶段与服务注册阶段的第6个阶段类似。服务客户端使用IPC应答数据中的Binder节点编号,进入服务使用阶段。
3.服务使用阶段
在服务使用阶段,服务客户端与拥有服务的Service Server进行IPC通信。通过在服务检索阶段获得的Binder节点编号,向Service Server发送IPC数据。在服务使用阶段中,发送IPC数据的进程是服务客户端,而接收端是Service Server.
3.1 传递Binder数据的阶段1
在服务使用阶段,Service Server进入待机状态,等待接收IPC数据。
3.2 传递Binder数据的阶段2
在服务使用阶段中,IPC数据包含与所用服务函数相关的RPC代码与RPC数据。在服务检索传递的Binder数据的第6个阶段中已经获取了服务的Binder节点编号,IPC数据中的Handle即是该Binder节点编号。
该阶段与服务注册的传递Binder数据的第2个阶段类似,但是与binder_transaction()函数查找接收端binder_proc结构体的过程有所不同。
在服务注册阶段,Handle值为0,查找到的是Context Manager的binder_proc结构体;而在服务使用阶段则使用target.handle,查找对应进程的binder_proc结构体。
调用binder_get_ref()函数,查找desc值为指定Handle的binder_ref结构体。查找到指定的结构体后,将binder_ref结构体的node指向Service Server的binder_node结构体
binder_node结构体的proc指向Service Server的binder_proc结构体,即接收IPC数据的进程
3.3 传递Binder数据的阶段3
该阶段与服务注册阶段类似,但接收IPC数据的不是Context Manager,而是Service Server. Service Server调用IPC数据中RPC代码所指的函数,并将RPC数据作为参数传入该函数中。此后的过程与Binder服务注册的传递Binder数据的第4,5,6阶段相同,不再赘述.
最后,欢迎大家关注我的微信公众号:AndroidGeek. AndroidGeek专注于Android源码解析、框架设计、最新技术分享以及职位招聘和内推。