SwallowJoe的博客

Be a real go-getter,
NEVER SETTLE!

0%

Binder(3)--sayHello之ioctl

简介

接上文,首先回顾一下IBinder相关接口的类图:

2_4_IBinder全类图

我们知道在Client App中获取的IBinder实际上是BinderProxy类型的对象。那么在上一文中Client App调用sayHello方法过程的的#2.3.2中,我们卡住了,现在可以继续了:

1
2
3
4
virtual status_t        transact(   uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0) = 0;

一. IBinder.transact

  1. Service.onServiceConnected
  2. IDemoInterface.Stub.Proxy.sayHello
  3. BinderProxy.transact(Stub.TRANSACTION_sayHello, ……)

1.1 BinderProxy.transact

1
2
3
4
5
6
7
8
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
// ......
try {
// 不多废话,直接开始, 注意我们现在的进程环境是Client App哦
return transactNative(code, data, reply, flags);
}
// ......
}

1.2 android_util_Binder#android_os_BinderProxy_transact

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
// ......
// 获取IBinder对象,这里我们已经知道了,是BinderProxy对应Native的IBinder对象
// 也就是对应Server App存入的JavaBBinder, 是Parcel:flattenBinder中存入cookie的BBinder
// 然后在通信过程中经过Binder驱动转成了对应的BpBinder
IBinder* target = getBPNativeData(env, obj)->mObject.get();

// ......

// 1.3 上一篇文章我们就分析了,这个target就是BpBinder
status_t err = target->transact(code, *data, reply, flags);

// ......
return JNI_FALSE;
}

1.3 BpBinder#transact

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// NOLINTNEXTLINE(google-default-arguments)
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// 一旦Binder对象死掉,就不会复活
if (mAlive) {
// ......

// 1.4 IPCThreadState!
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;

return status;
}

return DEAD_OBJECT;
}

1.4 IPCThreadState#transact

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
status_t err;

flags |= TF_ACCEPT_FDS;

// 1.4.1 将数据写入mOut中存储, cmd 是BC_TRANSACTION
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
// ......
if ((flags & TF_ONE_WAY) == 0) {
// TF_ONE_WAY 是代表这个binder调用是one_way的,不需要等待回传
// 这个分支表示需要回传数据
// ......
if (reply) {
// 1.5 当传入的reply Parcel不为null时
err = waitForResponse(reply);
} else {
// 当直接传入一个null的Parcel作为reply时
// 创建一个假的Parcel接收可能的回写数据
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
// ......
} else {
// 不需要回传数据
err = waitForResponse(nullptr, nullptr);
}

return err;
}

transaction的flag一共有四种:

name value function
TF_ONE_WAY 0x01 代表oneway的binder调用,不需要回传数据
TF_ROOT_OBJECT 0x04 内容是组件的根对象
TF_STATUS_CODE 0x08 内容是32位的状态代码
TF_ACCEPT_FDS 0x10 允许使用文件描述符答复

首先将需要传递的数据写入out中存储,然后去和binder driver通信。

这里我们先忽略IPCThreadState的初始化过程。

1.4.1 IPCThreadState#writeTransactionData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;

tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;

const status_t err = data.errorCheck();
if (err == NO_ERROR) {
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();
}
// ......
// cmd为BC_TRANSACTION
mOut.writeInt32(cmd);
// 将data保存在mOut中
mOut.write(&tr, sizeof(tr));

return NO_ERROR;
}

这里折后就是将数据保存在mOut中,这里是怎么保证多线程并发的时的处理呢?稍后我们研究IPC的初始化就知道了。

1.5 IPCThreadState#waitForResponse

从名字也可以猜出来,这里应该就是与binder.c驱动通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;

while (1) {
// 1.6 显而易见, 与驱动通信
if ((err=talkWithDriver()) < NO_ERROR) break;
// ......
// #1.6中已经通过ioctl与binder驱动通信了,回传的输入也写入了mIn中
// 读取的第一个uint32_t的数据代表binder通信类型
cmd = (uint32_t)mIn.readInt32();

// ......

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;
// 一般非oneway的通信就是走的这里
case BR_REPLY:
{
binder_transaction_data tr;
// 读取从binder驱动回传的数据
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中
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(nullptr,
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(nullptr,
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;
}

1.6 IPCThreadState#talkWithDriver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
status_t IPCThreadState::talkWithDriver(bool doReceive)
{

if (mProcess->mDriverFD < 0) {
return -EBADF;
}

// binder_write_read是一个保存了传输数据以及回传数据信息的结构体
binder_write_read bwr;

// 判断读缓冲区是否为空
const bool needRead = mIn.dataPosition() >= mIn.dataSize();

// 仍在从输入缓冲区中剩余的数据中读取数据,并且调用者已请求读取下一个数据,则不编写任何内容
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

// 将待传入的数据存入bwr中,以便通过binder驱动传输
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();

// 如果需要回传的数据时
if (doReceive && needRead) {
// 读缓冲区大小设置为可接受的最大大小
bwr.read_size = mIn.dataCapacity();
// 将bwr中读缓冲区指针指向mIn中的data,后续驱动直接将数据填充到这里
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}

// ......

do {
// .....
// 使用ioctl与binder驱动通信, 将bwr存储的信息传输给binder驱动
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
// .....
if (mProcess->mDriverFD < 0) {
err = -EBADF;
}
IF_LOG_COMMANDS() {
alog << "Finished read/write, write size = " << mOut.dataSize() << endl;
}
} while (err == -EINTR);

// ......

if (err >= NO_ERROR) {
// ......
if (bwr.read_consumed > 0) {
// 如果存在回传的数据,则标记
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
// ......
return NO_ERROR;
}

return err;
}

ok, 到这里我们对Binder通信已经有了一个初步的认知,最核心跨进程的通信手段是通过ioctl这个东东。

二. ioctl介绍

本身对Linux内核驱动不太了解,可以参考这篇博文: https://blog.csdn.net/qq_19923217/article/details/82698787

ioctl()系统调用操作特殊文件的底层设备参数。特别是,字符特殊文件(例如终端)的许多操作特性可以通过ioctl()请求来控制。

ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。

参数 描述
fd 文件描述符
cmd 交互协议,设备驱动将根据 cmd 执行对应操作
可变参数 arg,依赖 cmd 指定长度以及类型

2.1 API

  1. 头文件: #include <sys/ioctl.h>, 用于指定ioctl()请求的宏和定义位于文件<sys/ioctl.h>中。
  2. 参数: int ioctl(int fd, unsigned long request, …);
    1. fd 必须是打开文件描述符。
    2. cmd 是依赖于设备的请求代码, 即交互协议,设备驱动将根据 cmd 执行对应操作
    3. argp(…) 是指向内存的非类型指针, 它传统上是char*argp, ioctl()请求在其中编码了参数是in参数还是out参数,参数argp的大小以字节为单位。
  3. 返回值: ioctl() 函数执行成功时返回 0,失败则返回 -1 并设置全局变量 errorno 值
    1. EBADF fd不是有效的文件描述符。
    2. EFAULT 默认argp引用不可访问的内存区域。
    3. EINVAL 请求或argp无效。
    4. ENOTTY fd与字符专用设备不关联。
    5. ENOTTY 指定的请求不适用于文件描述符fd引用的对象类型。

2.2 ioctl用户与驱动之间的协议

参考:https://blog.csdn.net/zifehng/article/details/59576539

总结

通过这个sayHello的过程,我们对binder通信有了基本概念。知道BBinder, BpBinder, IBinder, IInterface等等类的作用。

接下来我们先看看Binder驱动的加载过程,之后在继续分析ioctl接下来的流程:比如Client发送请求后,binder驱动是怎么找到对应Server的。

参考资料

  1. Android Binder详解 https://mr-cao.gitbooks.io/android/content/android-binder.html
  2. msm-4.14 Code https://github.com/android-linux-stable/msm-4.14/blob/9c4b6ed1b229cfc35e5c3e5815e297b7f519cf93/drivers/android/binder.c
  3. linux 内核 - ioctl 函数详解 https://blog.csdn.net/qq_19923217/article/details/82698787
  4. ioctl(2) — Linux manual page https://man7.org/linux/man-pages/man2/ioctl.2.html
  5. ioctl()分析——从用户空间到设备驱动 https://blog.csdn.net/zifehng/article/details/59576539

欢迎关注我的其它发布渠道