FregServer进程,返回BR_REPLY

本文深入分析了Android系统中Binder机制的通信流程,重点讲解了进程间通信请求的处理过程及响应结果的返回机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文参考《Android系统源代码情景分析》,作者罗升阳

一、测试代码:

       ~/Android/external/binder/server

        ----FregServer.cpp

        ~/Android/external/binder/common

        ----IFregService.cpp

        ----IFregService.h

       ~/Android/external/binder/client

       ----FregClient.cpp


       Binder库(libbinder)代码:

       ~/Android/frameworks/base/libs/binder

       ----BpBinder.cpp

       ----Parcel.cpp

       ----ProcessState.cpp

       ----Binder.cpp

       ----IInterface.cpp

       ----IPCThreadState.cpp

       ----IServiceManager.cpp

       ----Static.cpp

       ~/Android/frameworks/base/include/binder

       ----Binder.h

       ----BpBinder.h

       ----IInterface.h

       ----IPCThreadState.h

       ----IServiceManager.h

       ----IBinder.h

       ----Parcel.h

       ----ProcessState.h


        驱动层代码:

       ~/Android//kernel/goldfish/drivers/staging/android

       ----binder.c

       ----binder.h


二、源码分析

      在Android Binder进程间通信---FregServer进程,发送BC_TRANSACTION,睡眠等待http://blog.youkuaiyun.com/jltxgcy/article/details/26076149一文中,在最后线程睡眠以等待进程间通信结果。

      在http://blog.youkuaiyun.com/jltxgcy/article/details/26076149文章中,我们假设请求注册Service组件FregService的线程thread正在Binder驱动程序函数binder_thread_read中等待Service Manager完成注册操作。现在既然Service Manager已经完成了对Service组件FregService的注册,并且线程thread已经被Binder驱动程序唤醒,接下来它就会执行函数binder_thread_read来处理它的todo队列中的工作项了。

      现在请求注册Service组件FregService的线程thread的todo队列中有一个类型为BINDER_WORK_TRANSACTION的工作项,因此,接下来我们就分析函数binder_thread_read处理这个工作项的过程。

       ~/Android//kernel/goldfish/drivers/staging/android

       ----binder.c

static int
binder_thread_read(struct binder_proc *proc, struct binder_thread *thread,
	void  __user *buffer, int size, signed long *consumed, int non_block)
{
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;
        .........

	while (1) {
		uint32_t cmd;
		struct binder_transaction_data tr;
		struct binder_work *w;
		struct binder_transaction *t = NULL;

		if (!list_empty(&thread->todo))
			w = list_first_entry(&thread->todo, struct binder_work, entry);//从线程thread的todo队列中取出这个类型为BINDER_WORK_TRANSACTION的工作项
		else if (!list_empty(&proc->todo) && wait_for_proc_work)
			w = list_first_entry(&proc->todo, struct binder_work, entry);
		else {
			..........
		}

		.....

		switch (w->type) {
		case BINDER_WORK_TRANSACTION: {
			t = container_of(w, struct binder_transaction, work);//接着又将该工作项的宿主binder_transaction结构体取回来,并且保存在变量t中
		} break;
		........
		if (t->buffer->target_node) {//NULL
			.......
		} else {
			tr.target.ptr = NULL;
			tr.cookie = NULL;
			cmd = BR_REPLY;
		}
		tr.code = t->code;//0
		tr.flags = t->flags;//0
		tr.sender_euid = t->sender_euid;

		if (t->from) {
			struct task_struct *sender = t->from->proc->tsk;
			tr.sender_pid = task_tgid_nr_ns(sender, current->nsproxy->pid_ns);
		} else {
			tr.sender_pid = 0;
		}

		tr.data_size = t->buffer->data_size;//数据缓冲区大小
		tr.offsets_size = t->buffer->offsets_size;//偏移数组大小
		tr.data.ptr.buffer = (void *)t->buffer->data + proc->user_buffer_offset;//内核缓冲区的内核空间地址和用户空间地址相差一个固定值,并且保存在它的成员变量user_buffer_offset中
		tr.data.ptr.offsets = tr.data.ptr.buffer + ALIGN(t->buffer->data_size, sizeof(void *));//偏移保存在数据缓冲区的后面

		if (put_user(cmd, (uint32_t __user *)ptr))//将命令返回
			return -EFAULT;
		ptr += sizeof(uint32_t);
		if (copy_to_user(ptr, &tr, sizeof(tr)))//将binder_transaction_data结构体tr返回
			return -EFAULT;
		ptr += sizeof(tr);

		......

		list_del(&t->work.entry);//删除该任务项
		t->buffer->allow_user_free = 1;//允许释放
		if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
			........
		} else {
			t->buffer->transaction = NULL;
			kfree(t);//释放binder_transaction结构体t
		        .........
		}
		break;
	}

done:

	*consumed = ptr - buffer;//cmd和binder_transaction_data结构体tr大小之和
	.......
	return 0;
}
      首先从线程thread的todo队列中取出这个类型为BINDER_WORK_TRANSACTION的工作项,并且保存在变量w中。接着又将该工作项的宿主binder_transaction结构体取回来,并且保存在变量t中。

      然后利用binder_transaction结构体t初始化binder_transaction_data结果体tr。然后将cmdBR_REPLY和tr返回用户空间。

  

      线程thread执行完成函数binder_thread_read之后,先返回到函数binder_ioctl中,接着再返回IPCThreadState类的成员函数talkWithDriver中,最后返回到IPCThreadState类的成员函数waitForResponse中来处理BR_REPLY返回协议,如下所示:

     ~/Android/frameworks/base/libs/binder

     ----IPCThreadState.cpp

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;

    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;
        .......
        
        cmd = mIn.readInt32();//cmd为BR_REPLY
        ......
        switch (cmd) {
        .......
        case BR_REPLY:
            {
                binder_transaction_data tr;
                err = mIn.read(&tr, sizeof(tr));//从协议缓冲区mIn的内容读到一个binder_transaction_data结构体tr中
                .........

                if (reply) {//不为NULL
                    if ((tr.flags & TF_STATUS_CODE) == 0) {//tr.flags为0
                        reply->ipcSetDataReference(//将保存在binder_transaction结构体tr中的进程间通信结果保存在Parcel对象reply中
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(size_t),
                            freeBuffer, this);
                    } else {
                       .......
                    }
                } else {
                   ........
                }
            }
            goto finish;
           .........
        }
    }

finish:
   .....
    return err;
}
       首先从返回协议缓冲区mIn中读取一个BR_REPLY返回协议代码。然后又从协议缓冲区mIn的内容读到一个binder_transaction_data结构体tr中。
       如果binder_transaction_data结构体tr的成员变量flags的TF_STATUS_CODE位等于0,就说明当前进程之前所发出的一个进程间通信请求已经被成功地处理了。因此,就将保存在binder_transaction结构体tr中的进程间通信结果保存在Parcel对象reply中,这是通过调用Parcel对象reply的成员函数ipcSetDataReference来实现的。实现如下:

     ~/Android/frameworks/base/libs/binder

     ----Parcel.cpp

void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
    const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)
{
    freeDataNoInit();
    mError = NO_ERROR;
    mData = const_cast<uint8_t*>(data);//进程间通信结果数据的缓冲区
    mDataSize = mDataCapacity = dataSize;进程间通信结果数据的缓冲区大小
    ..........
    mDataPos = 0;
    ..........
    mObjects = const_cast<size_t*>(objects);//偏移数组起始位置
    mObjectsSize = mObjectsCapacity = objectsCount;//偏移数组大小
    .........
    mOwner = relFunc;//freeBuffer
    mOwnerCookie = relCookie;//当前线程IPCThreadState对象
    ........
}
      参数data指向了用来保存进程间通信结果数据的缓冲区,它的大小由参数dataSize来描述。参数object指向了一个Binder对象偏移数组,用来描述保存在进程间通信数据缓冲区的Binder对象的位置,它的大小由参数objectsCount来描述,参数relFunc是一个函数指针,它指向了IPCTheadState类的成员函数freeBuffer,参数relCookie指向了当前线程的IPCThreadState对象。

      然后依次赋值给Parcel类的不同成员变量,mData是数据缓冲区的其实地址,mDataSize为数据缓冲区大小,mDataPos为下一个用来读入的位置。mObjects为偏移数组起始地址,mObjetctSize是偏移数组mObjects下一个用于读入数据的位置,mObjectsCapacity为偏移数组的大小。

      mOwner保存着freeBuffer的函数指针,mOwnerCookie保存者当前线程IPCThreadState对象。线程获得了保存在当前Parcel对象中的进程间通信结果数据之后,它就会析构该Parcel对象,而该Parcel对象在析构时,会调用它的成员变量mOwner和mOwnerCookie来调用IPCTheadState类的成员函数freeBuffer释放它内部使用的数据缓冲区mData。由于这个数据缓冲区是在Binder驱动程序中分配的,即它指向一个内核缓冲区,因此IPCThreadState类的成员函数freeBuffer会通过BC_FREE_BUFFER命令协议通知Binder驱动程序将内核缓冲区释放掉。

<think>我们在讨论Linux内核中的genlmsg_reply函数。首先,我们需要明确这个函数的作用和使用场景。genlmsg_reply是Linux内核中用于通用Netlink(Generic Netlink)通信的一个函数,它主要用于回复通过Netlink套接字接收到的消息。 ### 1. genlmsg_reply函数的作用 在Linux内核中,Netlink是一种用于内核与用户空间进程通信的机制。通用Netlink(Generic Netlink)是对Netlink的扩展,它提供了一种更简单的、基于家族的通信方式。当内核模块需要回复用户空间发送的请求时,可以使用`genlmsg_reply`函数。 具体来说,`genlmsg_reply`函数的作用是发送一个回复消息给请求方(通常是用户空间的进程)。它会自动设置消息头中的一些字段,例如消息序列号(sequence number)和端口ID(port ID),这些字段需要与请求消息匹配,以便用户空间能够正确识别回复。 ### 2. 函数原型 `genlmsg_reply`函数的原型通常如下(具体定义可能因内核版本而略有不同): ```c int genlmsg_reply(struct sk_buff *skb, struct genl_info *info); ``` - `skb`: 指向包含回复消息的套接字缓冲区(socket buffer)的指针。这个缓冲区应该已经通过`genlmsg_new`、`nlmsg_new`或其他相关函数分配,并且已经填充了回复内容。 - `info`: 指向`genl_info`结构的指针,该结构包含了接收到的请求消息的信息(如发送方的端口ID、序列号等)。 ### 3. 使用步骤 使用`genlmsg_reply`函数回复消息的一般步骤包括: 1. 分配一个套接字缓冲区(sk_buff)用于存放回复消息。 2. 在缓冲区中填充回复内容(包括Netlink消息头和具体的数据)。 3. 调用`genlmsg_reply`发送回复。 ### 4. 示例代码片段 下面是一个简化的示例,展示如何在Netlink操作的回调函数中使用`genlmsg_reply`: ```c #include <net/genetlink.h> // 定义Netlink操作的回调函数 static int my_operation(struct sk_buff *skb, struct genl_info *info) { struct sk_buff *reply_skb; void *reply_header; int ret; // 分配回复消息的缓冲区 reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); if (!reply_skb) return -ENOMEM; // 创建消息头 reply_header = genlmsg_put(reply_skb, info->snd_portid, info->snd_seq, &my_family, 0, MY_CMD_REPLY); if (!reply_header) { ret = -ENOMEM; goto fail; } // 添加属性(如果需要) // ... 使用nla_put系列函数填充数据 ... // 完成消息头的设置 genlmsg_end(reply_skb, reply_header); // 发送回复 ret = genlmsg_reply(reply_skb, info); if (ret < 0) { // 注意:genlmsg_reply在成功时会消耗skb,失败时我们需要释放 goto fail; } return 0; fail: nlmsg_free(reply_skb); return ret; } ``` 注意:在调用`genlmsg_reply`后,如果返回成功,则缓冲区`reply_skb`会被发送并自动释放,因此不需要再次释放。如果返回失败(例如网络不可达等),则需要手动释放缓冲区。 ### 5. 注意事项 - 在填充回复消息时,需要确保消息头中的端口ID(portid)和序列号(seq)与请求消息匹配,这些信息可以通过`info`参数获取。 - 回复消息的大小应该合理,避免分配过大的缓冲区。 - 错误处理非常重要,特别是在内存分配失败时。 ### 引用说明 在Linux内核中,`genlmsg_reply`函数的实现可以在`net/netlink/genetlink.c`文件中找到[^1]。该函数会调用`nlmsg_unicast`将消息发送给请求方。 §§相关问题§§ 1. 在Linux内核中,如何定义一个新的Generic Netlink家族? 2. `genlmsg_reply`和`nlmsg_unicast`有什么区别? 3. 如何处理在构建回复消息时发生的错误? 4. 用户空间如何接收和处理来自内核的Generic Netlink回复? [^1]: 参考Linux内核源码中的实现:`net/netlink/genetlink.c`。</think>### `genlmsg_reply` 函数详解 #### 1. **作用与功能** `genlmsg_reply` 是 Linux 内核中用于 **回复 Generic Netlink 请求** 的核心函数,属于 `net/netlink/genetlink.c` 的实现[^1]。主要功能包括: - **构造回复消息**:基于接收到的请求消息(`struct genl_info *info`)自动填充回复的 Netlink 消息头(如序列号、端口 ID)。 - **发送回复**:通过 `nlmsg_unicast()` 将回复消息发送回请求方(通常是用户空间进程)。 - **错误简化**:封装了 Netlink 消息构造和发送的底层细节,简化内核模块开发。 #### 2. **函数原型** ```c int genlmsg_reply(struct sk_buff *skb, struct genl_info *info); ``` - **参数**: - `skb`:包含回复数据的套接字缓冲区(需预先填充有效载荷)。 - `info`:指向接收到的请求消息的 `genl_info` 结构体(含发送方信息)。 - **返回值**: - 成功时返回 `0`。 - 失败时返回负错误码(如 `-ENOBUFS` 内存不足)。 #### 3. **使用流程** 以下是典型使用场景的代码框架: ```c // 1. 在 Netlink 操作的回调函数中处理请求 static int my_genl_cmd(struct sk_buff *skb, struct genl_info *info) { struct sk_buff *reply_skb; void *reply_header; // 2. 分配回复缓冲区 reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); if (!reply_skb) return -ENOMEM; // 3. 初始化消息头(自动填充序列号/端口ID) reply_header = genlmsg_put(reply_skb, info->snd_portid, info->snd_seq, &my_family, 0, MY_CMD_REPLY); if (!reply_header) goto fail; // 4. 填充回复数据(例如添加 Netlink 属性) nla_put_u32(reply_skb, MY_ATTR_STATUS, SUCCESS); // 5. 完成消息构造 genlmsg_end(reply_skb, reply_header); // 6. 发送回复 return genlmsg_reply(reply_skb, info); fail: nlmsg_free(reply_skb); return -EMSGSIZE; } ``` #### 4. **关键注意事项** - **缓冲区所有权**: 若 `genlmsg_reply()` 调用成功,**`skb` 会被内核接管并自动释放**;若失败,需手动释放 `skb`(如示例中的 `fail` 分支)。 - **端口 ID 与序列号**: 通过 `info->snd_portid` 和 `info->snd_seq` 确保回复与请求匹配,否则用户空间无法关联响应。 - **错误处理**: 常见错误包括: - `-ENOBUFS`:内存不足导致无法分配缓冲区。 - `-EINVAL`:无效的 `info` 参数或消息格式错误。 - `-ECONNREFUSED`:目标端口不可达。 #### 5. **典型应用场景** - 实现自定义的 **内核-用户空间通信协议**(如硬件状态查询、配置更新)。 - 响应 **用户空间工具的命令**(如 `iproute2` 通过 Netlink 配置网络)。 - 构建 **事件通知机制**(内核主动向用户空间推送事件)。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值