ARC的背景知识参考这篇文章 。
从背景知识可以知道,ARC是一个跨平台的Android runtime。Android前前后后修改了不少linux内核的东西,其中就包括binder。(PS:什么是binder?使用场景?这里、这里)。binder中有一个模块用到了内核驱动。(PS:如何写linux内核驱动?这里)这个模块是必不可少的IPC通讯模块。那么ARC如何处理这个模块的呢?
实现的代码在这里:
https://chromium.googlesource.com/chromium/src/+/69.0.3497.117/chromeos/binder/
(PS:熟悉代码的话,直接看代码就完事了,我这里写得不对的地方也欢迎校正。)
这里简单描述下实现。
ARC要用NaCl重新编译代码,其中包括libc,在libc中增加这部分的处理。
在用户空间上开一个共享内存,然后再构建一层IPC通讯机制。
把原来作为内核驱动的模块,在用户空间上做了实现。
提几个问题,带着问题来解释实现:
- 如何拦截open("/dev/binder",XXXX)、ioctl的系统调用?
- binder原来的功能是如何设计?
- 重新实现之后有哪些出入?
- 如何独立的测试/修改这部分功能?
如何拦截原来的系统调用
const char kDriverPath[] = "/dev/binder";
bool Driver::Initialize() {
base::AssertBlockingAllowed();
// Open binder driver.
fd_.reset(HANDLE_EINTR(open(kDriverPath, O_RDWR | O_CLOEXEC | O_NONBLOCK)));
if (!fd_.is_valid()) {
PLOG(ERROR) << "Failed to open";
return false;
}
// Version check.
int version = 0;
if (HANDLE_EINTR(ioctl(fd_.get(), BINDER_VERSION, &version)) != 0 ||
version != BINDER_CURRENT_PROTOCOL_VERSION) {
PLOG(ERROR) << "Version check failure: version = " << version;
return false;
}
...
}
注意到这里有一个 HANDLE_EINTR
,它对这个 /dev/binder
文件的系统调用 ( open、ioctl、poll )替换下实现?
// TODO test
binder driver原来的设计
binder driver为跨进程的数据交换做了支持,其中主要是如下几个方法。
参考
http://gityuan.com/2016/09/04/binder-start-service/#三binder-driver
binder_ioctl
首先,根据传递过来的文件句柄指针获取相应的binder_proc结构体, 再从中查找binder_thread,如果当前线程已经加入到proc的线程队列则直接返回, 如果不存在则创建binder_thread,并将当前线程添加到当前的proc.
当返回值为-ENOMEM,则意味着内存不足,往往会出现创建binder_thread对象失败;
当返回值为-EINVAL,则意味着CMD命令参数无效;
binder_ioctl_write_read
/binder_thread_write
此时arg是一个binder_write_read结构体,mOut数据保存在write_buffer,所以write_size>0,但此时read_size=0。首先,将用户空间bwr结构体拷贝到内核空间,然后执行binder_thread_write()操作.
不断从binder_buffer所指向的地址获取cmd, 当只有BC_TRANSACTION或者BC_REPLY时, 则调用binder_transaction()来处理事务.
binder_transaction
- 查询目标进程的过程: handle -> binder_ref -> binder_node -> binder_proc
- 将BINDER_WORK_TRANSACTION添加到目标队列target_list:
call事务, 则目标队列target_list=target_proc->todo;
reply事务,则目标队列target_list=target_thread->todo;
async事务,则目标队列target_list=target_node->async_todo. - 数据拷贝
将用户空间binder_transaction_data中ptr.buffer和ptr.offsets拷贝到目标进程的binder_buffer->data;
这就是只拷贝一次的真理所在; - 设置事务栈信息
BC_TRANSACTION且非oneway, 则将当前事务添加到thread->transaction_stack; - 事务分发过程:
将BINDER_WORK_TRANSACTION添加到目标队列(此时为target_proc->todo队列);
将BINDER_WORK_TRANSACTION_COMPLETE添加到当前线程thread->todo队列; - 唤醒目标进程target_proc开始执行事务。
该方法中proc/thread是指当前发起方的进程信息,而binder_proc是指目标接收端进程。 此时当前线程thread的todo队列已经有事务, 接下来便会进入binder_thread_read来处理相关的事务.
重新实现之后的出入
// TODO
代码测试于验证
// TODO