网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ProcessState::ProcessState(const char *driver)
{
if (mDriverFD >= 0) {
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
}
}
注意mmap上方的注释,已经说的很清楚了,这块内存映射只是作为接收transactions来使用的,也就是说往驱动写数据的时候是与内存映射无关的。记住这一点。
下面我们看一下内核空间相应的调用:
**binder\_mmap**
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
…
ret = binder_alloc_mmap_handler(&proc->alloc, vma);
…
}
真正的映射操作由函数binder\_alloc\_mmap\_handler()完成。这里我们只需要记住这个函数的第一个入参&proc->alloc。通过这个结构体我们就能找到映射好的内存块。
内存映射完成之后,就让我们看一下Binder的传输过程中哪里使用到了这块特殊的内存。
### Binder传输过程
传输过程我们只关注内存和数据。
**发起方用户空间**
发起方用户空间做的事情其实就像发快递一样不停的打包,注意一下都包了些啥。
**IPCThreadState::writeTransactionData**
// 我们要传输的数据在data这个入参中
status_t IPCThreadState::writeTransactionData(… const Parcel& data…)
{
binder_transaction_data tr;
…
//tr.data.ptr.buffer保存了指向data的指针
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
…
// 将tr写入mOut。
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
这里tr只保存了指向数据的指针。然后tr被写入mOut这个Parcel。
**IPCThreadState::talkWithDriver**
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
…
binder_write_read bwr;
...
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();
...
bwr.write_consumed = 0;
...
ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0
...
}
这里又包了一层,bwr。其中bwr.write\_buffer保存了指向mOut.data()的指针。在这里也就是指向了tr。
所以在发起方:
* bwr含有指向tr的指针。
* tr含有指向data的指针。
记住上面两点,接下来我们看一下内核空间是怎么取包的:
**发起方内核空间**
**binder\_ioctl\_write\_read**
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread **threadp)
{ …
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
…
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
…
ret = binder_thread_write(proc, *threadp,
bwr.write_buffer,
bwr.write_size,
&bwr.write_consumed);
}
这里我们遇到了第一个copy\_from\_user()调用。这个调用会把用户空间的bwr给拷贝到内核空间。但是要注意,copy\_from\_user()的第一个入参是拷贝的目标地址,这里给的是&bwr,函数内部的一个结构体。显然此处和内存映射没有关系。接下来就进入binder\_thread\_write。入参有bwr.write\_buffer,回头看用户空间最底层那里,指向的是不是tr?
###### binder\_thread\_write
static int binder_thread_write(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed)
{
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
void __user *ptr = buffer + *consumed;
…
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, 0);
break;
}
....
}
这里我们遇到了第二个`copy_from_user()`。这回会把用户空间的那个`tr`,也就是`IPCThreadState.mOut`,给拷贝到内核中来,看它的第一个入参,还是和内存映射没有关系。接下来就进入关键的`binder_transaction()`了。
##### binder\_transaction
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply,
binder_size_t extra_buffers_size)
{
…
struct binder_transaction *t;
…
t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
tr->offsets_size, extra_buffers_size,
!reply && (t->flags & TF_ONE_WAY));
…
copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
tr->data.ptr.buffer, tr->data_size)
…
off_start = (binder_size_t *)(t->buffer->data +
ALIGN(tr->data_size, sizeof(void *)));
offp = off_start;
…
copy_from_user(offp, (const void __user *)(uintptr_t)
tr->data.ptr.offsets, tr->offsets_size);
}
首先看一下`t->buffer`,函数`binder_alloc_new_buf()`的返回值会赋值给它,这里是我们第一次见到这个函数,从名字看是分配内存,看它的第一个入参`&target_proc->alloc`。现在回想前面说`mmap`的时候提到内存映射的信息会保存到`proc->alloc`这个结构体中。所以这里我们就可以确定现在是在接收方进程的内存映射中分配了一块内存出来。`t->buffer`就指向这块有映射的内存。
接下来就是我们遇到的第三次`copy_from_user()`调用了。回想在用户空间的时候`tr.data.ptr.buffer`是指向我们要传输的数据的。所以这里可以看到这个`copy_from_user()`的操作就是*把发起方用户空间的数据直接拷贝到了接收方内核的内存映射中。* 这就是所谓“一次拷贝”的关键点。
紧接着还有一个`copy_from_user()`调用,这里拷贝的是和数据相关的一些跨境程对象的偏移量,和前面拷贝`bwr`和`tr`在体量上来讲与数据的体量相比不是主要矛盾,所以说“一次拷贝”指的就是上面对数据的拷贝。
至此关于“一次拷贝”这个问题我们应该是已经有了初步的答案了,但为了让整个过程形成个闭环,接下来我们再来看一下Binder传输过程的后半段。
##### 接收方内核空间
###### binder\_thread\_read
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread **threadp,
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;
…
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 *));
…
copy_to_user(ptr, &tr, sizeof(tr));
}
这里我们遇到了第一个`copy_to_user()`调用,这是把`tr`给拷贝到接收方的用户空间的`IPCThreadState.mIn`。在此之前把内核映射的数据地址指针转换为用户空间的指针赋值给`tr.data.ptr.buffer`。
###### binder\_ioctl\_write\_read
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread **threadp)
{
…
copy_to_user(ubuf, &bwr, sizeof(bwr));
…
}
最后我们遇到了第二个`copy_to_user()`。把`bwr`又拷贝回用户空间了,注意此时`bwr`内包含指向`tr`的指针。也就是`bwr.read_buffer`是指向这个`tr`,或者说`IPCThreadState.mIn`。
##### 接收方用户空间
接下来就回到接收方的用户空间了:
###### IPCThreadState::executeCommand
status_t IPCThreadState::executeCommand(int32_t cmd)
{
…
case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
…
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),
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
1Emm-1715832781580)]
[外链图片转存中…(img-ySJ7k1Ok-1715832781580)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!