当前位置: 首页 > news >正文

深入内核讲明白Android Binder【一】

深入内核讲明白Android Binder【一】

    • 前言
    • 一、Android Binder应用编写概述
    • 二、基于C语言编写Android Binder跨进程通信Demo
      • 0. Demo简介
      • 1. 服务的管理者server_manager.c
      • 2. Binder服务端代码实现 test_service.c
        • 2.1 实现思路
        • 2.2 完整实现代码
      • 3. Binder客户端代码实现 test_client.c
        • 3.1 实现思路
        • 3.2 完整实现代码
    • 三、Binder Demo源码解析
      • 1. test_service.c源码解析
        • 1.1 打开驱动
        • 1.2 向service_manager注册服务
          • 1. bio_init(&msg, iodata, sizeof(iodata), 4);
          • 2. bio_put_uint32(&msg, 0); // strict mode header
          • 3. bio_put_string16_x(&msg, SVC_MGR_NAME);
          • 4. bio_put_string16_x(&msg, name);
          • 5. bio_put_obj(&msg, ptr);
          • 6. binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE)
        • 1.3 死循环监测client端发送的数据
          • 0. 进程间交互CMD介绍
          • 1. 读取客户端发来的数据
          • 2. 解析客户端发来的数据并处理和回复
      • 2. test_client.c源码解析
          • 1. 从service_manager获取服务service
          • 2. 向服务server发送数据,获得返回值
      • 3. service_manager.c源码解析
        • 1. 告诉binder驱动它是service_manager
        • 2. 死循环等待请求
          • 2.1 读取数据
          • 2.2 解析并处理数据
            • 2.2.1 注册服务
            • 2.2.2 获取服务

前言

Android Binder基于Binder驱动实现跨进程通信,它是Android进程之间通信的桥梁,是整个Android系统的基石。本文会基于C语言先编写Binder的应用Demo,再基于Demo一步步深入内核分析Binder驱动源码,讲明白Android Binder的工作原理,对Android Binder做到既知其然,又知其所以然。深挖到内核剖析Binder驱动源码,这个技术点还是非常多的,全部写在一篇文章,篇幅太长,容易打消学习的积极性。因此我拆分两篇文章完成讲解,这是开篇,主要讲解如何用Binder编写跨进程的应用,并对编写的Demo进行源码解析,但解析止步于linux内核。至于深入到linux内核部分的源码分析,我们在下一篇文章再进行分析

特别鸣谢:韦东山老师的Binder课程
本文讲解的Binder示例直接采用韦东山老师课程中的示例


我以往也很少写研究源码的文章,但现在越来越明白阅读优秀源码的重要性,这是从Android源码层次讲解Android技术的开篇文章,后面会继续讲解很多源码级别的技术,这里就多聊一下自己的想法。

Binder其实大部分人都会用,那么会用不就行了吗?为啥要花大量的时间研究源码呢?其实以前我也是这个想法,觉得既然别人已经把轮子造好了,我只要能用好这个轮子就可以了,何必关心轮子具体是怎么造的?其实有这种想法的,只能说我们对于Android开发还是处于表面,暂时未接触到更深入的Android技术,但现在IT行业,特别是Android行业,仅仅停留在表面的开发技术,已然随时会被淘汰,因为表面的技术门槛太低了,这种技术人员已经饱和,若工作了很多年,却依然不知由表及里,停留在啃表面经验,那只能被淘汰!

而想要技术由表及里,就需要对以往经常使用“轮子”,特别是那些“基础轮子”,进行深入研究,不仅要知道怎么用,更要知道它是怎么造出来的。而Binder就是Android世界中最最基础的轮子,以往我们只知道用这个轮子,现在我们要更深一步的拆开这个轮子,一点点剖析清楚它的制造细节。

学习本文不仅能够深入理解Binder的工作原理,更灵活的运用Binder,而且更能够提升你阅读源码的能力,提升“语感”,优秀源码读的多了,技术基础就更加的扎实,技术思路就更加广泛,帮助你脱离技术表面,技术能力更进一阶。

学习技术的过程有点像张三丰教张无忌太极拳一样,先是记住招式,再是逐步忘记招式,最后无招胜有招。
学习一项技术也一样,先是熟练运用这项技术,知道它使用的所有步骤;然后,深入技术内核,明白这个技术的原理;最后就算你忘记了技术使用的步骤,但依然能够基于原理在无意识中正确的使用该技术。

一、Android Binder应用编写概述

上面我们提到Android Binder是进行跨进程通信的技术,也就是A进程要访问B进程所提供的方法,这里我们称A进程为客户端Client,B进程为服务端Server,且B进程提供的方法称为服务Service。

Android系统中的内存分为两部分,一部分是用户空间,一部分是内核空间。不同进程具有不同的用户空间,无法相互访问数据,但是不同进程都可以通过系统调用,如ioctl等去访问内核空间,而驱动程序就是在内核空间中运行,并且可以直接与硬件交互。Binder跨进程通信就是用户空间的进程,通过使用Binder驱动程序,把数据放到内核空间去中转,实现不同进程之间的数据传递。

编写Binder应用涉及三个进程:

  1. 服务端进程test_server.c用户提供服务
  2. 客户端进程test_client.c用于使用服务端提供的服务
  3. service_manager.c用户管理服务。

其中service_manager是Android系统中已经编写好的程序,我们直接使用即可,我们只需要编写自己的服务端和客户端的代码。

为什么需要service_manager?上面我们提到了,Binder跨进程通信,其实就是把数据放到内核空间中转,实现数据的传递,但是Android中的服务太多了,如果没有一个进程去管理这些服务,那内核中的数据将杂乱无章,难以管理,因此,Android系统中有一个service_manager进程,这个进程就是用户管理服务的注册和获取的。后面我们会分析service_manager的源码,搞清楚它到底是如何管理服务的。

点击查看service_manager源码

如上分析,我们将基于C语言编写Android Binder跨进程通信Demo,需要实现test_client作为客户端,test_server作为服务端,且服务端提供sayhello和sayhello_to两个服务,进程间数据传递的概览过程如下图所示:
在这里插入图片描述
Binder只需要一次拷贝的核心就在于每个进程打开Binder驱动后,都会通过mmap实现一块映射到内核空间的内存,这块内存,用户空间可以直接使用,而不用先从内核空间拷贝到用户空间。

二、基于C语言编写Android Binder跨进程通信Demo

Android源码framework/native/cmds/servicemanager下是一个C语言实现Binder跨进程通信的半成品bctest.c,我们可以参考bctest.c编写自己的服务。点击查看源代码

0. Demo简介

  • Demo中实现一个服务端test_server,它提供hello服务,服务中包含sayhello和sayhello_to两个函数实现;
  • Demo中实现一个客户端test_client,它使用服务端test_server的sayhello/sayhello_to函数

1. 服务的管理者server_manager.c

这个类不需要我们自己写,系统已经写好了,源码在framework/native/cmds/servicemanager/server_manager.c,我们只需要写服务提供者test_server.c和服务使用者test_client.c即可,虽然我们不用写service_manager,但后面我们会分析它的源码,它的实现逻辑大致如下:

  1. 打开binder驱动
  2. 告诉驱动它是service_manager
  3. 死循环等待请求
    3.1 读取数据
    3.2 解析数据
    3.3 处理数据(添加/获取服务)

2. Binder服务端代码实现 test_service.c

2.1 实现思路
  1. 打开驱动
  2. 向service_manager注册服务
  3. 死循环监测client端发送的数据
    3.1 读数据
    3.2 解析数据+处理数据
    3.3 回复数据
2.2 完整实现代码

下面我们会对该代码进行详细分析。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/types.h>
#include<stdbool.h>
#include <string.h>#include <private/android_filesystem_config.h>#include "binder.h"
#include "test_server.h"int svcmgr_publish(struct binder_state *bs, uint32_t target, const char *name, void *ptr)
{int status;unsigned iodata[512/4];struct binder_io msg, reply;bio_init(&msg, iodata, sizeof(iodata), 4);bio_put_uint32(&msg, 0);  // strict mode headerbio_put_string16_x(&msg, SVC_MGR_NAME);bio_put_string16_x(&msg, name);bio_put_obj(&msg, ptr);if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE))return -1;status = bio_get_uint32(&reply);binder_done(bs, &msg, &reply);return status;
}void sayhello(void)
{static int cnt = 0;fprintf(stderr, "say hello : %d\n", ++cnt);
}int sayhello_to(char *name)
{static int cnt = 0;fprintf(stderr, "say hello to %s : %d\n", name, ++cnt);return cnt;
}int hello_service_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{/* 根据txn->code知道要调用哪一个函数* 如果需要参数, 可以从msg取出* 如果要返回结果, 可以把结果放入reply*//* sayhello* sayhello_to*/uint16_t *s;char name[512];size_t len;uint32_t handle;uint32_t strict_policy;int i;// Equivalent to Parcel::enforceInterface(), reading the RPC// header with the strict mode policy mask and the interface name.// Note that we ignore the strict_policy and don't propagate it// further (since we do no outbound RPCs anyway).strict_policy = bio_get_uint32(msg);switch(txn->code) {case HELLO_SVR_CMD_SAYHELLO:sayhello();bio_put_uint32(reply, 0); /* no exception */return 0;case HELLO_SVR_CMD_SAYHELLO_TO:/* 从msg里取出字符串 */s = bio_get_string16(msg, &len);  //"IHelloService"s = bio_get_string16(msg, &len);  // nameif (s == NULL) {return -1;}for (i = 0; i < len; i++)name[i] = s[i];name[i] = '\0';/* 处理 */i = sayhello_to(name);/* 把结果放入reply */bio_put_uint32(reply, 0); /* no exception */bio_put_uint32(reply, i);break;default:fprintf(stderr, "unknown code %d\n", txn->code);return -1;}return 0;
}int test_server_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{int (*handler)(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply);//从txn->target.ptr获得函数指针handler = (int (*)(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply))txn->target.ptr;//执行函数指针return handler(bs, txn, msg, reply);
}int main(int argc, char **argv)
{int fd;struct binder_state *bs;uint32_t svcmgr = BINDER_SERVICE_MANAGER;uint32_t handle;int ret;bs = binder_open(128*1024);if (!bs) {fprintf(stderr, "failed to open binder driver\n");return -1;}/* add service */ret = svcmgr_publish(bs, svcmgr, "hello", hello_service_handler);if (ret) {fprintf(stderr, "failed to publish hello service\n");return -1;}#if 0while (1){/* read data *//* parse data, and process *//* reply */}
#endifbinder_set_maxthreads(bs, 10);//这个是参照service_manager写的,因为service_manager也是一个服务端,因此它的代码可以借鉴binder_loop(bs, test_server_handler);return 0;
}

3. Binder客户端代码实现 test_client.c

3.1 实现思路
  1. 打开驱动
  2. 从service_manager获取服务service
  3. 向服务server发送数据,获得返回值
3.2 完整实现代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/types.h>
#include<stdbool.h>
#include <string.h>#include <private/android_filesystem_config.h>#include "binder.h"
#include "test_server.h"uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)
{uint32_t handle;unsigned iodata[512/4];struct binder_io msg, reply;bio_init(&msg, iodata, sizeof(iodata), 4);bio_put_uint32(&msg, 0);  // strict mode headerbio_put_string16_x(&msg, SVC_MGR_NAME);bio_put_string16_x(&msg, name);if (binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE))return 0;handle = bio_get_ref(&reply);if (handle)binder_acquire(bs, handle);binder_done(bs, &msg, &reply);return handle;
}struct binder_state *g_bs;
uint32_t g_hello_handle;
uint32_t g_goodbye_handle;void sayhello(void)
{unsigned iodata[512/4];struct binder_io msg, reply;/* 构造binder_io */bio_init(&msg, iodata, sizeof(iodata), 4);bio_put_uint32(&msg, 0);  // strict mode headerbio_put_string16_x(&msg, "IHelloService");/* 放入参数 *//* 调用binder_call */if (binder_call(g_bs, &msg, &reply, g_hello_handle, HELLO_SVR_CMD_SAYHELLO))return ;/* 从reply中解析出返回值 */binder_done(g_bs, &msg, &reply);}int sayhello_to(char *name)
{unsigned iodata[512/4];struct binder_io msg, reply;int ret;int exception;/* 构造binder_io */bio_init(&msg, iodata, sizeof(iodata), 4);bio_put_uint32(&msg, 0);  // strict mode headerbio_put_string16_x(&msg, "IHelloService");/* 放入参数 */bio_put_string16_x(&msg, name);/* 调用binder_call */if (binder_call(g_bs, &msg, &reply, g_hello_handle, HELLO_SVR_CMD_SAYHELLO_TO))return 0;/* 从reply中解析出返回值 */exception = bio_get_uint32(&reply);if (exception)ret = -1;elseret = bio_get_uint32(&reply);binder_done(g_bs, &msg, &reply);return ret;}/* ./test_client hello* ./test_client hello <name>*/int main(int argc, char **argv)
{int fd;struct binder_state *bs;uint32_t svcmgr = BINDER_SERVICE_MANAGER;uint32_t handle;int ret;if (argc < 2){fprintf(stderr, "Usage:\n");fprintf(stderr, "%s <hello|goodbye>\n", argv[0]);fprintf(stderr, "%s <hello|goodbye> <name>\n", argv[0]);return -1;}bs = binder_open(128*1024);if (!bs) {fprintf(stderr, "failed to open binder driver\n");return -1;}g_bs = bs;//向service_manager发送数据,获得hello服务句柄handle = svcmgr_lookup(bs, svcmgr, "hello");if (!handle) {fprintf(stderr, "failed to get hello service\n");return -1;}g_hello_handle = handle;fprintf(stderr, "Handle for hello service = %d\n", g_hello_handle);/* send data to server */if (!strcmp(argv[1], "hello")){if (argc == 2) {sayhello();} else if (argc == 3) {ret = sayhello_to(argv[2]);fprintf(stderr, "get ret of sayhello_to = %d\n", ret);		}}binder_release(bs, handle);return 0;
}

三、Binder Demo源码解析

用户空间整体流程如下:
在这里插入图片描述

1. test_service.c源码解析

先从入口函数main函数分析。

int main(int argc, char **argv)
{int fd;struct binder_state *bs;// BINDER_SERVICE_MANAGER是0uint32_t svcmgr = BINDER_SERVICE_MANAGER;uint32_t handle;int ret;//打开驱动bs = binder_open(128*1024);if (!bs) {fprintf(stderr, "failed to open binder driver\n");return -1;}//向service_manager添加服务ret = svcmgr_publish(bs, svcmgr, "hello", hello_service_handler);if (ret) {fprintf(stderr, "failed to publish hello service\n");return -1;}//设置该服务的最大线程数binder_set_maxthreads(bs, 10);//死循环,等待解析和处理客户端发来的数据binder_loop(bs, test_server_handler);return 0;
}

从上面的代码也验证了上面我们提到的服务端实现思路,即服务端的实现包括以下几个步骤:

  1. 打开驱动
  2. 向service_manager注册服务
  3. 死循环监测client端发送的数据

下面我们分析源码看看这几个步骤具体都干了啥

1.1 打开驱动
bs = binder_open(128*1024);
if (!bs) {fprintf(stderr, "failed to open binder driver\n");return -1;
}

点击查看binder_open源码

  1. 分配一块binder_state类型的内存
  2. 打开驱动
  3. 设置映射到内核Binder驱动中一块内存的大小 传入的是128*1024,即128Kb
  4. 映射一块驱动内存。后续test_server进程可以直接获取该块内存,而不用先由内核空间拷贝到用户空间,省去一次拷贝过程
struct binder_state *binder_open(size_t mapsize)
{struct binder_state *bs;struct binder_version vers;//分配一块binder_state类型的内存bs = malloc(sizeof(*bs));if (!bs) {errno = ENOMEM;return NULL;}//打开驱动bs->fd = open("/dev/binder", O_RDWR);if (bs->fd < 0) {fprintf(stderr,"binder: cannot open device (%s)\n",strerror(errno));goto fail_open;}//判断驱动版本是否兼容if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||(vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {fprintf(stderr, "binder: driver version differs from user space\n");goto fail_open;}//映射到内核中一块内存的大小 传入的是128*1024,即128Kbbs->mapsize = mapsize;//映射一块内核Binder驱动的内存。后续test_server进程可以直接获取该块内存,而不用先由内核空间拷贝到用户空间,省去一次拷贝过程bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);if (bs->mapped == MAP_FAILED) {fprintf(stderr,"binder: cannot map device (%s)\n",strerror(errno));goto fail_map;}return bs;fail_map:close(bs->fd);
fail_open:free(bs);return NULL;
}
1.2 向service_manager注册服务
/* add service */
ret = svcmgr_publish(bs, svcmgr, "hello", hello_service_handler);
if (ret) {fprintf(stderr, "failed to publish hello service\n");return -1;
}
  1. 构造binder_io数据
  2. 调用binder_call向service_manager发送数据
int svcmgr_publish(struct binder_state *bs, uint32_t target, const char *name, void *ptr)
{int status;// msg实际存储数据的缓存空间,即512个字节,512*8B=4Kbunsigned iodata[512/4];struct binder_io msg, reply;// 划分iodata缓存给msg,iodata的前16个字节存储其它数据头,剩余的缓存空间用于存储需要发送的数据bio_init(&msg, iodata, sizeof(iodata), 4);// 从iodata的第17个字节开始,占用4个字节的空间写入数据0,同时更新msg->data的指针位置,以及msg->data_avail的剩余有效缓存大小bio_put_uint32(&msg, 0);  // strict mode header// #define SVC_MGR_NAME "android.os.IServiceManager"/* 写入service_manager的名称"android.os.IServiceManager"内存存储格式:首先占用4个字节写入字符串的长度,然后每个字符占用2个字节,写入字符串*/bio_put_string16_x(&msg, SVC_MGR_NAME);// 写入要注册的服务名称“hello”bio_put_string16_x(&msg, name);// ptr是函数地址,构造flat_binder_object对象,将ptr写入flat_binder_object->binderbio_put_obj(&msg, ptr);// 调用binder_call向service_manager发送数据if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE))return -1;status = bio_get_uint32(&reply);binder_done(bs, &msg, &reply);return status;
}
1. bio_init(&msg, iodata, sizeof(iodata), 4);
void bio_init(struct binder_io *bio, void *data,size_t maxdata, size_t maxoffs)
{//size_t是无符号整型,32位系统上,占用4个字节,64位系统上,占用8个字节。//我们代码分析都按照4个字节计算。//上面传进来的maxoffs等于4,那么n就等于16,即16个字节size_t n = maxoffs * sizeof(size_t);if (n > maxdata) {bio->flags = BIO_F_OVERFLOW;bio->data_avail = 0;bio->offs_avail = 0;return;}//bio->data = bio->data0都指向data第17个字节,即bio的数据是从data的第17个字节开始存储的,前16个字节用于存储其它数据bio->data = bio->data0 = (char *) data + n;//bio->offs = bio->offs0指向data内存的起点,后面可以通过offs来操作前16个字节空间bio->offs = bio->offs0 = data;//bio->data_avail,bio中data的有效数据长度,即512-16 = 406字节,也就是除了前16个字节,其余都是用于存储bio的data数据bio->data_avail = maxdata - n;//记录有效的偏移量是4,这些偏移空间用来存储data以外的数据bio->offs_avail = maxoffs;bio->flags = 0;
}// Binder.h
struct binder_io
{char *data;            /* pointer to read/write from */binder_size_t *offs;   /* array of offsets */size_t data_avail;     /* bytes available in data buffer */size_t offs_avail;     /* entries available in offsets array */char *data0;           /* start of data buffer */binder_size_t *offs0;  /* start of offsets buffer */uint32_t flags;uint32_t unused;
};
  • 内存分布状态
    在这里插入图片描述
2. bio_put_uint32(&msg, 0); // strict mode header
void bio_put_uint32(struct binder_io *bio, uint32_t n)
{//sizeof(n)=4字节,分配bio的缓存uint32_t *ptr = bio_alloc(bio, sizeof(n));if (ptr)//向缓存中写入整数0,这个整数占用4个字节大小*ptr = n;
}static void *bio_alloc(struct binder_io *bio, size_t size)
{size = (size + 3) & (~3);if (size > bio->data_avail) {bio->flags |= BIO_F_OVERFLOW;return NULL;} else {void *ptr = bio->data;//bio->data指向的空间向右移动4个字节bio->data += size;//更新剩余有效数据缓存大小bio->data_avail -= size;return ptr;}
}
  • 内存分布状态
    在这里插入图片描述
3. bio_put_string16_x(&msg, SVC_MGR_NAME);
void bio_put_string16_x(struct binder_io *bio, const char *_str)
{unsigned char *str = (unsigned char*) _str;size_t len;uint16_t *ptr;if (!str) {bio_put_uint32(bio, 0xffffffff);return;}len = strlen(_str);if (len >= (MAX_BIO_SIZE / sizeof(uint16_t))) {bio_put_uint32(bio, 0xffffffff);return;}/* Note: The payload will carry 32bit size instead of size_t *///将字符串的长度写入缓存,占4个字节bio_put_uint32(bio, len);//移动bio->data指针的位置,分配缓存,每两个字节存储一个字符,同时更新剩余有效缓存的大小ptr = bio_alloc(bio, (len + 1) * sizeof(uint16_t));if (!ptr)return;//向缓存中逐个写入字符while (*str)*ptr++ = *str++;*ptr++ = 0;
}
  • 内存分布状态
    在这里插入图片描述
    在这里插入图片描述
4. bio_put_string16_x(&msg, name);

源代码和上面一样,这里不再重复贴

  • 内存分布状态
    在这里插入图片描述
5. bio_put_obj(&msg, ptr);
void bio_put_obj(struct binder_io *bio, void *ptr)
{struct flat_binder_object *obj;// 为flat_binder_object分配内存,并将obj指针指向这块内存,同时offs记录flat_binder_object的内存相对位置obj = bio_alloc_obj(bio);if (!obj)return;// 向flat_binder_object内存中写数据obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;obj->type = BINDER_TYPE_BINDER;obj->binder = (uintptr_t)ptr;obj->cookie = 0;
}static struct flat_binder_object *bio_alloc_obj(struct binder_io *bio)
{struct flat_binder_object *obj;// 为flat_binder_object分配内存obj = bio_alloc(bio, sizeof(*obj));// 更新偏移指针offs,指向flat_binder_object的内存起点减去偏移内存长度if (obj && bio->offs_avail) {// offs_avail共有4个字节,用掉一个,就要更新可用偏移量bio->offs_avail--;// offs从内存起点开始*bio->offs++ = ((char*) obj) - ((char*) bio->data0); //指向obj(相对位置,只是为了便于理解)return obj;}bio->flags |= BIO_F_OVERFLOW;return NULL;
}// Binder.h
struct flat_binder_object {struct binder_object_header	hdr;__u32				flags;/* 8 bytes of data. */union {binder_uintptr_t	binder;	/* local object */__u32			handle;	/* remote object */};/* extra data associated with local object */binder_uintptr_t	cookie;
};
  • 内存分布状态

在这里插入图片描述

6. binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE)
  • msg是之前组织好的数据
  • reply是用于存储服务端返回的数据
  • target = 0,代表要发送msg数据的目的进程,0代表service_manager
  • SVC_MGR_ADD_SERVICE 是一个枚举值,代码要调用service_manager服务中添加服务的函数
int binder_call(struct binder_state *bs,struct binder_io *msg, struct binder_io *reply,uint32_t target, uint32_t code)
{int res;struct binder_write_read bwr;struct {uint32_t cmd;struct binder_transaction_data txn;} __attribute__((packed)) writebuf;unsigned readbuf[32];if (msg->flags & BIO_F_OVERFLOW) {fprintf(stderr,"binder: txn buffer overflow\n");goto fail;}writebuf.cmd = BC_TRANSACTION;//ioclt类型writebuf.txn.target.handle = target;//数据发送给哪个进程writebuf.txn.code = code;//调用进程的哪个函数writebuf.txn.flags = 0;writebuf.txn.data_size = msg->data - msg->data0;//数据本身大小writebuf.txn.offsets_size = ((char*) msg->offs) - ((char*) msg->offs0);//数据头大小,指向binder_node实体(发送端提供服务函数的地址),bio_put_obj(&msg, ptr);writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0;//指向数据本身内存起点writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0;//指向数据头内存起点bwr.write_size = sizeof(writebuf);bwr.write_consumed = 0;bwr.write_buffer = (uintptr_t) &writebuf;hexdump(msg->data0, msg->data - msg->data0);for (;;) {bwr.read_size = sizeof(readbuf);bwr.read_consumed = 0;bwr.read_buffer = (uintptr_t) readbuf;res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//调用ioctl发送数据给驱动程序if (res < 0) {fprintf(stderr,"binder: ioctl failed (%s)\n", strerror(errno));goto fail;}res = binder_parse(bs, reply, (uintptr_t) readbuf, bwr.read_consumed, 0);if (res == 0) return 0;if (res < 0) goto fail;}fail:memset(reply, 0, sizeof(*reply));reply->flags |= BIO_F_IOERROR;return -1;
}struct binder_write_read {binder_size_t		write_size;	/* bytes to write */binder_size_t		write_consumed;	/* bytes consumed by driver */binder_uintptr_t	write_buffer;binder_size_t		read_size;	/* bytes to read */binder_size_t		read_consumed;	/* bytes consumed by driver */binder_uintptr_t	read_buffer;
};struct binder_transaction_data {/* The first two are only used for bcTRANSACTION and brTRANSACTION,* identifying the target and contents of the transaction.*/union {/* target descriptor of command transaction */__u32	handle;/* target descriptor of return transaction */binder_uintptr_t ptr;} target;binder_uintptr_t	cookie;	/* target object cookie */__u32		code;		/* transaction command *//* General information about the transaction. */__u32	        flags;__kernel_pid_t	sender_pid;__kernel_uid32_t	sender_euid;binder_size_t	data_size;	/* number of bytes of data */binder_size_t	offsets_size;	/* number of bytes of offsets *//* If this transaction is inline, the data immediately* follows here; otherwise, it ends with a pointer to* the data buffer.*/union {struct {/* transaction data */binder_uintptr_t	buffer;/* offsets from buffer to flat_binder_object structs */binder_uintptr_t	offsets;} ptr;__u8	buf[8];} data;
};
1.3 死循环监测client端发送的数据
int test_server_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{//定义函数指针int (*handler)(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply);// 从txn->target.ptr;handler = (int (*)(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply))txn->target.ptr;return handler(bs, txn, msg, reply);
}binder_loop(bs, test_server_handler);
0. 进程间交互CMD介绍

这里先给出进程间交互用到的CMD(即用于判断进程操作类型的标识)结论,后面会结合源码验证。

  • 进程发送数据时CMD都是BC_XXX
  • 进程接收数据时CMD都是BR_XXX
  • 只有BC_TRANSACTION,BR_TRANSACTION,BC_REPLY,BR_REPLY涉及两进程。其它所有的cmd(BC_XXX,BR_XXX)只是app和驱动的交互,用于改变和报告状态
    在这里插入图片描述
1. 读取客户端发来的数据
void binder_loop(struct binder_state *bs, binder_handler func)
{int res;struct binder_write_read bwr;uint32_t readbuf[32];// write_size=0,说明不是向服务端发数据,而是向客户端读数据bwr.write_size = 0;bwr.write_consumed = 0;bwr.write_buffer = 0;readbuf[0] = BC_ENTER_LOOPER;//将readbuf写入内核驱动,至于怎么写的,后续分析binder_write(bs, readbuf, sizeof(uint32_t));for (;;) {bwr.read_size = sizeof(readbuf);bwr.read_consumed = 0;bwr.read_buffer = (uintptr_t) readbuf;// 读取客户端发来的数据,客户端发送的数据就在服务端映射到内核驱动的那块内存上res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);if (res < 0) {ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));break;}// 服务端解析客户端发来的数据并处理,func就是服务端的处理函数res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);if (res == 0) {ALOGE("binder_loop: unexpected reply?!\n");break;}if (res < 0) {ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));break;}}
}
2. 解析客户端发来的数据并处理和回复
  • 解析客户端发来的数据
/*
ptr:读取到的数据缓存地址
size:读取到的数据大小
func:服务端用于处理数据的函数
*/
int binder_parse(struct binder_state *bs, struct binder_io *bio,uintptr_t ptr, size_t size, binder_handler func)
{int r = 1;uintptr_t end = ptr + (uintptr_t) size;while (ptr < end) {uint32_t cmd = *(uint32_t *) ptr;ptr += sizeof(uint32_t);
#if TRACEfprintf(stderr,"%s:\n", cmd_name(cmd));
#endif//服务端是接受来自客户端的数据,因此cmd是BR_TRANSACTIONswitch(cmd) {case BR_NOOP:break;case BR_TRANSACTION_COMPLETE:break;case BR_INCREFS:case BR_ACQUIRE:case BR_RELEASE:case BR_DECREFS:
#if TRACEfprintf(stderr,"  %p, %p\n", (void *)ptr, (void *)(ptr + sizeof(void *)));
#endifptr += sizeof(struct binder_ptr_cookie);break;case BR_SPAWN_LOOPER: {//binder请求服务创建新线程/* create new thread *///if (fork() == 0) {//}pthread_t thread;struct binder_thread_desc btd;btd.bs = bs;btd.func = func;pthread_create(&thread, NULL, binder_thread_routine, &btd);/* in new thread: ioctl(BC_ENTER_LOOPER), enter binder_looper */break;}case BR_TRANSACTION: {//获取的客户端发送来的binder_transaction_data数据struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;if ((end - ptr) < sizeof(*txn)) {ALOGE("parse: txn too small!\n");return -1;}binder_dump_txn(txn);if (func) {unsigned rdata[256/4];struct binder_io msg;struct binder_io reply;int res;// 构造用于回复数据的binder_io reply,这个函数在前面分析过,不再赘述bio_init(&reply, rdata, sizeof(rdata), 4);// 将txn转换为binder_io数据msg,这里的msg就是客户端发送binder_io数据bio_init_from_txn(&msg, txn);// 调用服务端处理函数test_server_handlerres = func(bs, txn, &msg, &reply);//服务端函数处理完数据,把需要回复给客户端的数据reply发送给客户端binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);}ptr += sizeof(*txn);break;}case BR_REPLY: {struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;if ((end - ptr) < sizeof(*txn)) {ALOGE("parse: reply too small!\n");return -1;}binder_dump_txn(txn);if (bio) {bio_init_from_txn(bio, txn);bio = 0;} else {/* todo FREE BUFFER */}ptr += sizeof(*txn);r = 0;break;}case BR_DEAD_BINDER: {struct binder_death *death = (struct binder_death *)(uintptr_t) *(binder_uintptr_t *)ptr;ptr += sizeof(binder_uintptr_t);death->func(bs, death->ptr);break;}case BR_FAILED_REPLY:r = -1;break;case BR_DEAD_REPLY:r = -1;break;default:ALOGE("parse: OOPS %d\n", cmd);return -1;}}return r;
}void bio_init_from_txn(struct binder_io *bio, struct binder_transaction_data *txn)
{bio->data = bio->data0 = (char *)(intptr_t)txn->data.ptr.buffer;bio->offs = bio->offs0 = (binder_size_t *)(intptr_t)txn->data.ptr.offsets;bio->data_avail = txn->data_size;bio->offs_avail = txn->offsets_size / sizeof(size_t);bio->flags = BIO_F_SHARED;
}
  • 处理数据:根据客户端的数据,调用服务端的函数进行处理
int test_server_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{int (*handler)(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply);//从txn->target.ptr获得函数指针handler = (int (*)(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply))txn->target.ptr;//执行函数指针,这里函数指针指向的是服务端的hello_service_handler函数return handler(bs, txn, msg, reply);
}int hello_service_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{/* 根据txn->code知道要调用哪一个函数* 如果需要参数, 可以从msg取出* 如果要返回结果, 可以把结果放入reply*//* sayhello* sayhello_to*/uint16_t *s;char name[512];size_t len;uint32_t handle;uint32_t strict_policy;int i;// Equivalent to Parcel::enforceInterface(), reading the RPC// header with the strict mode policy mask and the interface name.// Note that we ignore the strict_policy and don't propagate it// further (since we do no outbound RPCs anyway).strict_policy = bio_get_uint32(msg);//客户端发送来的数据包含code类型switch(txn->code) {case HELLO_SVR_CMD_SAYHELLO://执行服务端的sayhello函数sayhello();//向reply中写入数据0bio_put_uint32(reply, 0); /* no exception */return 0;case HELLO_SVR_CMD_SAYHELLO_TO:/* 从msg里取出字符串 */s = bio_get_string16(msg, &len);  //"IHelloService"s = bio_get_string16(msg, &len);  // nameif (s == NULL) {return -1;}for (i = 0; i < len; i++)name[i] = s[i];name[i] = '\0';/* 调用服务端sayhello_to函数处理 */i = sayhello_to(name);/* 把结果放入reply */bio_put_uint32(reply, 0); /* no exception */bio_put_uint32(reply, i);break;default:fprintf(stderr, "unknown code %d\n", txn->code);return -1;}return 0;
}void sayhello(void)
{static int cnt = 0;fprintf(stderr, "say hello : %d\n", ++cnt);
}int sayhello_to(char *name)
{static int cnt = 0;fprintf(stderr, "say hello to %s : %d\n", name, ++cnt);return cnt;
}
  • 回复数据:服务端向客户端回复数据
void binder_send_reply(struct binder_state *bs,struct binder_io *reply,binder_uintptr_t buffer_to_free,int status)
{struct {uint32_t cmd_free;binder_uintptr_t buffer;uint32_t cmd_reply;struct binder_transaction_data txn;} __attribute__((packed)) data;data.cmd_free = BC_FREE_BUFFER;//server拷贝到service_manager映射的内核态缓冲区的数据,用完后,就可以释放了data.buffer = buffer_to_free;// 回复数据cmd是BC_REPLYdata.cmd_reply = BC_REPLY;data.txn.target.ptr = 0;data.txn.cookie = 0;data.txn.code = 0;if (status) {data.txn.flags = TF_STATUS_CODE;data.txn.data_size = sizeof(int);data.txn.offsets_size = 0;data.txn.data.ptr.buffer = (uintptr_t)&status;data.txn.data.ptr.offsets = 0;} else {// 组织binder_transaction_data数据data.txn.flags = 0;data.txn.data_size = reply->data - reply->data0;data.txn.offsets_size = ((char*) reply->offs) - ((char*) reply->offs0);data.txn.data.ptr.buffer = (uintptr_t)reply->data0;data.txn.data.ptr.offsets = (uintptr_t)reply->offs0;}//发送数据binder_write(bs, &data, sizeof(data));
}int binder_write(struct binder_state *bs, void *data, size_t len)
{struct binder_write_read bwr;int res;bwr.write_size = len;bwr.write_consumed = 0;bwr.write_buffer = (uintptr_t) data;bwr.read_size = 0;bwr.read_consumed = 0;bwr.read_buffer = 0;/*向客户端发送数据我怎么知道客户端是谁?其实服务端都收到客户端的数据了,自然知道客户端是谁,后面分析transaction_stack会详细介绍*/res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);if (res < 0) {fprintf(stderr,"binder_write: ioctl failed (%s)\n",strerror(errno));}return res;
}

2. test_client.c源码解析

先从入口函数main函数分析。

int main(int argc, char **argv)
{int fd;struct binder_state *bs;uint32_t svcmgr = BINDER_SERVICE_MANAGER;uint32_t handle;int ret;if (argc < 2){fprintf(stderr, "Usage:\n");fprintf(stderr, "%s <hello|goodbye>\n", argv[0]);fprintf(stderr, "%s <hello|goodbye> <name>\n", argv[0]);return -1;}//打开驱动bs = binder_open(128*1024);if (!bs) {fprintf(stderr, "failed to open binder driver\n");return -1;}g_bs = bs;//向service_manager发送数据,获得hello服务句柄handle = svcmgr_lookup(bs, svcmgr, "hello");if (!handle) {fprintf(stderr, "failed to get hello service\n");return -1;}g_hello_handle = handle;fprintf(stderr, "Handle for hello service = %d\n", g_hello_handle);/* 向服务端发送数据 */if (!strcmp(argv[1], "hello")){if (argc == 2) {sayhello();} else if (argc == 3) {ret = sayhello_to(argv[2]);fprintf(stderr, "get ret of sayhello_to = %d\n", ret);		}}binder_release(bs, handle);return 0;
}

从上述代码也可以验证我们第二部分提到的客户端实现思路:

  1. 打开驱动
  2. 从service_manager获取服务service
  3. 向服务server发送数据,获得返回值

打开驱动的源码我们上面分析过了,这里不再赘述。

1. 从service_manager获取服务service
handle = svcmgr_lookup(bs, svcmgr, "hello");
if (!handle) {fprintf(stderr, "failed to get hello service\n");return -1;
}
g_hello_handle = handle;
fprintf(stderr, "Handle for hello service = %d\n", g_hello_handle);
  • handle = svcmgr_lookup(bs, svcmgr, “hello”);
/*
target:0 service_manager服务的handle
name:"hello"
即,向service_manager服务获取hello服务
*/
uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)
{uint32_t handle;unsigned iodata[512/4];struct binder_io msg, reply;// 这里构造的数据和上面向service_manager注册服务时构造的数据一致,上面已经详细介绍,不再赘述bio_init(&msg, iodata, sizeof(iodata), 4);bio_put_uint32(&msg, 0);  // strict mode headerbio_put_string16_x(&msg, SVC_MGR_NAME);bio_put_string16_x(&msg, name);// 构造数据发送给service_manager,调用service_manager的SVC_MGR_CHECK_SERVICE方法,获取服务if (binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE))return 0;// service_manager返回hello服务对应的句柄,后续可以通过handle在service_manager中找到hello服务handle = bio_get_ref(&reply);if (handle)binder_acquire(bs, handle);binder_done(bs, &msg, &reply);return handle;
}

binder_call函数在上面service_manager注册服务的时已经分析过,这里不再赘述。区别点在于解析数据时,客户端的CMD是BR_REPLY

int binder_call(struct binder_state *bs,struct binder_io *msg, struct binder_io *reply,uint32_t target, uint32_t code)
{int res;struct binder_write_read bwr;struct {uint32_t cmd;struct binder_transaction_data txn;} __attribute__((packed)) writebuf;unsigned readbuf[32];if (msg->flags & BIO_F_OVERFLOW) {fprintf(stderr,"binder: txn buffer overflow\n");goto fail;}writebuf.cmd = BC_TRANSACTION;//ioclt类型writebuf.txn.target.handle = target;//数据发送给哪个进程writebuf.txn.code = code;//调用进程的哪个函数writebuf.txn.flags = 0;writebuf.txn.data_size = msg->data - msg->data0;//数据本身大小writebuf.txn.offsets_size = ((char*) msg->offs) - ((char*) msg->offs0);//数据头大小,指向binder_node实体(发送端提供服务函数的地址),bio_put_obj(&msg, ptr);writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0;//指向数据本身内存起点writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0;//指向数据头内存起点bwr.write_size = sizeof(writebuf);bwr.write_consumed = 0;bwr.write_buffer = (uintptr_t) &writebuf;hexdump(msg->data0, msg->data - msg->data0);for (;;) {bwr.read_size = sizeof(readbuf);bwr.read_consumed = 0;bwr.read_buffer = (uintptr_t) readbuf;res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//调用ioctl发送数据给驱动程序if (res < 0) {fprintf(stderr,"binder: ioctl failed (%s)\n", strerror(errno));goto fail;}res = binder_parse(bs, reply, (uintptr_t) readbuf, bwr.read_consumed, 0);if (res == 0) return 0;if (res < 0) goto fail;}fail:memset(reply, 0, sizeof(*reply));reply->flags |= BIO_F_IOERROR;return -1;
}int binder_parse(struct binder_state *bs, struct binder_io *bio,uintptr_t ptr, size_t size, binder_handler func)
{int r = 1;uintptr_t end = ptr + (uintptr_t) size;while (ptr < end) {uint32_t cmd = *(uint32_t *) ptr;ptr += sizeof(uint32_t);
#if TRACEfprintf(stderr,"%s:\n", cmd_name(cmd));
#endif// 客户端收到回复CMD是BR_REPLYswitch(cmd) {case BR_NOOP:break;case BR_TRANSACTION_COMPLETE:break;case BR_INCREFS:case BR_ACQUIRE:case BR_RELEASE:case BR_DECREFS:
#if TRACEfprintf(stderr,"  %p, %p\n", (void *)ptr, (void *)(ptr + sizeof(void *)));
#endifptr += sizeof(struct binder_ptr_cookie);break;case BR_SPAWN_LOOPER: {//binder请求服务创建新线程/* create new thread *///if (fork() == 0) {//}pthread_t thread;struct binder_thread_desc btd;btd.bs = bs;btd.func = func;pthread_create(&thread, NULL, binder_thread_routine, &btd);/* in new thread: ioctl(BC_ENTER_LOOPER), enter binder_looper */break;}case BR_TRANSACTION: {struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;if ((end - ptr) < sizeof(*txn)) {ALOGE("parse: txn too small!\n");return -1;}binder_dump_txn(txn);if (func) {unsigned rdata[256/4];struct binder_io msg;struct binder_io reply;int res;bio_init(&reply, rdata, sizeof(rdata), 4);bio_init_from_txn(&msg, txn);res = func(bs, txn, &msg, &reply);binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);}ptr += sizeof(*txn);break;}case BR_REPLY: {// 获取到服务端发送来的binder_transaction_data数据struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;if ((end - ptr) < sizeof(*txn)) {ALOGE("parse: reply too small!\n");return -1;}binder_dump_txn(txn);if (bio) {// 解析binder_transaction_data数据,存储到binder_io中bio_init_from_txn(bio, txn);bio = 0;} else {/* todo FREE BUFFER */}ptr += sizeof(*txn);r = 0;break;}case BR_DEAD_BINDER: {struct binder_death *death = (struct binder_death *)(uintptr_t) *(binder_uintptr_t *)ptr;ptr += sizeof(binder_uintptr_t);death->func(bs, death->ptr);break;}case BR_FAILED_REPLY:r = -1;break;case BR_DEAD_REPLY:r = -1;break;default:ALOGE("parse: OOPS %d\n", cmd);return -1;}}return r;
}
2. 向服务server发送数据,获得返回值
if (!strcmp(argv[1], "hello"))
{if (argc == 2) {sayhello();} else if (argc == 3) {ret = sayhello_to(argv[2]);fprintf(stderr, "get ret of sayhello_to = %d\n", ret);		}
}void sayhello(void)
{unsigned iodata[512/4];struct binder_io msg, reply;/* 构造binder_io */bio_init(&msg, iodata, sizeof(iodata), 4);bio_put_uint32(&msg, 0);  // strict mode headerbio_put_string16_x(&msg, "IHelloService");/* 放入参数 *//* 调用binder_call,向hello服务的sayhello发送数据  */if (binder_call(g_bs, &msg, &reply, g_hello_handle, HELLO_SVR_CMD_SAYHELLO))return ;/* 从reply中解析出返回值 */binder_done(g_bs, &msg, &reply);}int sayhello_to(char *name)
{unsigned iodata[512/4];struct binder_io msg, reply;int ret;int exception;/* 构造binder_io */bio_init(&msg, iodata, sizeof(iodata), 4);bio_put_uint32(&msg, 0);  // strict mode headerbio_put_string16_x(&msg, "IHelloService");/* 放入参数 */bio_put_string16_x(&msg, name);/* 调用binder_call,向hello服务的sayhello_to发送数据 */if (binder_call(g_bs, &msg, &reply, g_hello_handle, HELLO_SVR_CMD_SAYHELLO_TO))return 0;/* 从reply中解析出返回值 */exception = bio_get_uint32(&reply);if (exception)ret = -1;elseret = bio_get_uint32(&reply);binder_done(g_bs, &msg, &reply);return ret;}

核心代码在上面都已经讲过了,这里不再重复,无非就是构造binder_io数据,然后通过binder_call向hello服务的指定函数发送数据

3. service_manager.c源码解析

上面提到过service_manager不需要我们自己写,系统已经写好了,源码在framework/native/cmds/servicemanager/server_manager.c,根据上面源码的分析我们知道service_manager有两项主要能力:

  1. 注册服务:如上面源码分析中提到,test_server需要向service_manager注册服务
  2. 获取服务:如上面源码分析中提到,test_client需要向service_manager获取服务

因为无论是服务端向service_manager注册服务,还是客户端向service_manager获取服务,都是跨进程通讯,都需要调用ioctl,经过内核程序才能到达service_manager,而考虑到内核知识点太多,为了避免篇幅过长,本篇文章暂不分析内核Binder驱动代码,留到下一篇文章再分析,因此,上面的源码分析到ioct就不再继续分析了,导致上面讲解注册服务和获取服务时,都没有进入service_manager源码继续分析。
那么,本节我们也是只分析service_manager的注册服务和获取服务的源码,而不必考虑服务端或者客户端的数据是如何发送给service_manager的,这在下一篇内核源码分析会讲解。

分析service_manager源码,同样我们先从入口函数main函数分析。

int main(int argc, char **argv)
{struct binder_state *bs;bs = binder_open(128*1024);//打开binder驱动if (!bs) {ALOGE("failed to open binder driver\n");return -1;}if (binder_become_context_manager(bs)) {//告诉驱动它是service_managerALOGE("cannot become context manager (%s)\n", strerror(errno));return -1;}svcmgr_handle = BINDER_SERVICE_MANAGER;binder_loop(bs, svcmgr_handler);//死循环等待请求return 0;
}

从上面的代码也能验证我们之前提到的service_manager的实现逻辑:

  1. 打开binder驱动
  2. 告诉驱动它是service_manager
  3. 死循环等待请求
    3.1 读取数据
    3.2 解析数据
    3.3 处理数据(注册/获取服务)

打开驱动的代码之前已经分析过,这里不再赘述。

1. 告诉binder驱动它是service_manager
int binder_become_context_manager(struct binder_state *bs)
{   //内核驱动程序源码,下一篇文章分析return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
2. 死循环等待请求
//死循环一直监测客户端发送来的数据
binder_loop(bs, svcmgr_handler);//service_manager服务的处理函数
int svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{struct svcinfo *si;uint16_t *s;size_t len;uint32_t handle;uint32_t strict_policy;int allow_isolated;//ALOGI("target=%x code=%d pid=%d uid=%d\n",//  txn->target.handle, txn->code, txn->sender_pid, txn->sender_euid);if (txn->target.handle != svcmgr_handle)return -1;if (txn->code == PING_TRANSACTION)return 0;// Equivalent to Parcel::enforceInterface(), reading the RPC// header with the strict mode policy mask and the interface name.// Note that we ignore the strict_policy and don't propagate it// further (since we do no outbound RPCs anyway).strict_policy = bio_get_uint32(msg);s = bio_get_string16(msg, &len);  //传入的是android.os.IServiceManagerif (s == NULL) {return -1;}if ((len != (sizeof(svcmgr_id) / 2)) ||memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {//传入的必须是android.os.IServiceManagerfprintf(stderr,"invalid id %s\n", str8(s, len));return -1;}switch(txn->code) {case SVC_MGR_GET_SERVICE:case SVC_MGR_CHECK_SERVICE:s = bio_get_string16(msg, &len);if (s == NULL) {return -1;}handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid);if (!handle)break;bio_put_ref(reply, handle);return 0;case SVC_MGR_ADD_SERVICE:s = bio_get_string16(msg, &len);//获得服务名if (s == NULL) {return -1;}handle = bio_get_ref(msg);//获得服务引用号handleallow_isolated = bio_get_uint32(msg) ? 1 : 0;//添加服务if (do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid))return -1;break;case SVC_MGR_LIST_SERVICES: {uint32_t n = bio_get_uint32(msg);if (!svc_can_list(txn->sender_pid)) {ALOGE("list_service() uid=%d - PERMISSION DENIED\n",txn->sender_euid);return -1;}si = svclist;while ((n-- > 0) && si)si = si->next;if (si) {bio_put_string16(reply, si->name);return 0;}return -1;}default:ALOGE("unknown code %d\n", txn->code);return -1;}bio_put_uint32(reply, 0);//处理完后,最后要构造一个reply,并放入0return 0;
}
2.1 读取数据
void binder_loop(struct binder_state *bs, binder_handler func)
{int res;struct binder_write_read bwr;uint32_t readbuf[32];bwr.write_size = 0;bwr.write_consumed = 0;bwr.write_buffer = 0;readbuf[0] = BC_ENTER_LOOPER;binder_write(bs, readbuf, sizeof(uint32_t));for (;;) {bwr.read_size = sizeof(readbuf);bwr.read_consumed = 0;bwr.read_buffer = (uintptr_t) readbuf;// 读取客户端发送的数据res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);if (res < 0) {ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));break;}// 解析客户端的数据res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);if (res == 0) {ALOGE("binder_loop: unexpected reply?!\n");break;}if (res < 0) {ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));break;}}
}
2.2 解析并处理数据
/*
func:service_manager实现的服务函数svcmgr_handler
*/
int binder_parse(struct binder_state *bs, struct binder_io *bio,uintptr_t ptr, size_t size, binder_handler func)
{int r = 1;uintptr_t end = ptr + (uintptr_t) size;while (ptr < end) {uint32_t cmd = *(uint32_t *) ptr;ptr += sizeof(uint32_t);
#if TRACEfprintf(stderr,"%s:\n", cmd_name(cmd));
#endif   // 客户端发送给service_manager的数据,service_manager收到时CMD类型是BR_TRANSACTIONswitch(cmd) {case BR_NOOP:break;case BR_TRANSACTION_COMPLETE:break;case BR_INCREFS:case BR_ACQUIRE:case BR_RELEASE:case BR_DECREFS:
#if TRACEfprintf(stderr,"  %p, %p\n", (void *)ptr, (void *)(ptr + sizeof(void *)));
#endifptr += sizeof(struct binder_ptr_cookie);break;case BR_SPAWN_LOOPER: {//binder请求服务创建新线程/* create new thread *///if (fork() == 0) {//}pthread_t thread;struct binder_thread_desc btd;btd.bs = bs;btd.func = func;pthread_create(&thread, NULL, binder_thread_routine, &btd);/* in new thread: ioctl(BC_ENTER_LOOPER), enter binder_looper */break;}case BR_TRANSACTION: {// 获取客户端发来的binder_transaction_datastruct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;if ((end - ptr) < sizeof(*txn)) {ALOGE("parse: txn too small!\n");return -1;}binder_dump_txn(txn);if (func) {unsigned rdata[256/4];struct binder_io msg;struct binder_io reply;int res;// 初始化replybio_init(&reply, rdata, sizeof(rdata), 4);// 将binder_transaction_data转为binder_iobio_init_from_txn(&msg, txn);// 执行本地处理函数svcmgr_handlerres = func(bs, txn, &msg, &reply);// 把处理结果回复给客户端binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);}ptr += sizeof(*txn);break;}case BR_REPLY: {struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;if ((end - ptr) < sizeof(*txn)) {ALOGE("parse: reply too small!\n");return -1;}binder_dump_txn(txn);if (bio) {bio_init_from_txn(bio, txn);bio = 0;} else {/* todo FREE BUFFER */}ptr += sizeof(*txn);r = 0;break;}case BR_DEAD_BINDER: {struct binder_death *death = (struct binder_death *)(uintptr_t) *(binder_uintptr_t *)ptr;ptr += sizeof(binder_uintptr_t);death->func(bs, death->ptr);break;}case BR_FAILED_REPLY:r = -1;break;case BR_DEAD_REPLY:r = -1;break;default:ALOGE("parse: OOPS %d\n", cmd);return -1;}}return r;
}// service_manager本地实现的服务函数
int svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{struct svcinfo *si;uint16_t *s;size_t len;uint32_t handle;uint32_t strict_policy;int allow_isolated;//ALOGI("target=%x code=%d pid=%d uid=%d\n",//  txn->target.handle, txn->code, txn->sender_pid, txn->sender_euid);if (txn->target.handle != svcmgr_handle)return -1;if (txn->code == PING_TRANSACTION)return 0;// Equivalent to Parcel::enforceInterface(), reading the RPC// header with the strict mode policy mask and the interface name.// Note that we ignore the strict_policy and don't propagate it// further (since we do no outbound RPCs anyway).strict_policy = bio_get_uint32(msg);s = bio_get_string16(msg, &len);  //传入的是android.os.IServiceManagerif (s == NULL) {return -1;}if ((len != (sizeof(svcmgr_id) / 2)) ||memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {//传入的必须是android.os.IServiceManagerfprintf(stderr,"invalid id %s\n", str8(s, len));return -1;}switch(txn->code) {case SVC_MGR_GET_SERVICE:case SVC_MGR_CHECK_SERVICE:s = bio_get_string16(msg, &len);if (s == NULL) {return -1;}handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid);if (!handle)break;bio_put_ref(reply, handle);return 0;case SVC_MGR_ADD_SERVICE:s = bio_get_string16(msg, &len);//获得服务名if (s == NULL) {return -1;}handle = bio_get_ref(msg);//获得服务引用号handleallow_isolated = bio_get_uint32(msg) ? 1 : 0;//添加服务if (do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid))return -1;break;case SVC_MGR_LIST_SERVICES: {uint32_t n = bio_get_uint32(msg);if (!svc_can_list(txn->sender_pid)) {ALOGE("list_service() uid=%d - PERMISSION DENIED\n",txn->sender_euid);return -1;}si = svclist;while ((n-- > 0) && si)si = si->next;if (si) {bio_put_string16(reply, si->name);return 0;}return -1;}default:ALOGE("unknown code %d\n", txn->code);return -1;}bio_put_uint32(reply, 0);//处理完后,最后要构造一个reply,并放入0return 0;
}
2.2.1 注册服务

客户端向service_manager注册服务时发送的code值是SVC_MGR_ADD_SERVICE,这在上面的源码分析中可知。

  • 向svclist链表中添加注册服务的信息
/*
msg和txn都是客户端发送的数据
*/
int svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{......case SVC_MGR_ADD_SERVICE:s = bio_get_string16(msg, &len);//获得服务名if (s == NULL) {return -1;}//获得服务引用号handle=0handle = bio_get_ref(msg);// 注册服务时并没有写入该值,因此allow_isolated为0allow_isolated = bio_get_uint32(msg) ? 1 : 0;//添加服务if (do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid))return -1;break;......
}
  1. bio_get_ref(msg);
uint32_t bio_get_ref(struct binder_io *bio)
{struct flat_binder_object *obj;// 获取flat_binder_object,这个对象中存储了服务端需要注册的函数指针obj = _bio_get_obj(bio);// 注册服务时,写入了flat_binder_object,这点在test_server源码分析中可知,因此obj不是NULLif (!obj)return 0;if (obj->type == BINDER_TYPE_HANDLE)// 返回 handlereturn obj->handle;return 0;
}static struct flat_binder_object *_bio_get_obj(struct binder_io *bio)
{size_t n;// 从这儿就验证了之前在test_server源码分析中bio_put_obj写入数据时,offs是一个相对位置size_t off = bio->data - bio->data0;/* TODO: be smarter about this? */for (n = 0; n < bio->offs_avail; n++) {if (bio->offs[n] == off)// 如果有offs指针指向off,代表数据中有flat_binder_object,那么我们获取这个flat_binder_objectreturn bio_get(bio, sizeof(struct flat_binder_object));}bio->data_avail = 0;bio->flags |= BIO_F_OVERFLOW;return NULL;
}static void *bio_get(struct binder_io *bio, size_t size)
{size = (size + 3) & (~3);if (bio->data_avail < size){bio->data_avail = 0;bio->flags |= BIO_F_OVERFLOW;return NULL;}  else {void *ptr = bio->data;bio->data += size;bio->data_avail -= size;return ptr;}
}
  1. do_add_service(bs, s, len, handle, txn->sender_euid, allow_isolated, txn->sender_pid)
/*
s:服务名 hello
len:服务名的长度
allow_isolated: 0
*/
int do_add_service(struct binder_state *bs,const uint16_t *s, size_t len,uint32_t handle, uid_t uid, int allow_isolated,pid_t spid)
{struct svcinfo *si;//ALOGI("add_service('%s',%x,%s) uid=%d\n", str8(s, len), handle,//        allow_isolated ? "allow_isolated" : "!allow_isolated", uid);if (!handle || (len == 0) || (len > 127))return -1;if (!svc_can_register(s, len, spid)) {ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",str8(s, len), handle, uid);return -1;}// 查找svclist中是否已经有名字为s的服务si = find_svc(s, len);if (si) {if (si->handle) {ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",str8(s, len), handle, uid);svcinfo_death(bs, si);}// 如果s服务已经存在,则覆盖之前的服务si->handle = handle;} else {//如果链表中查不到svcinfo,则构造svcinfosi = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));if (!si) {ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",str8(s, len), handle, uid);return -1;}si->handle = handle;si->len = len;memcpy(si->name, s, (len + 1) * sizeof(uint16_t));si->name[len] = '\0';si->death.func = (void*) svcinfo_death;si->death.ptr = si;si->allow_isolated = allow_isolated;// 将其放在链表的表头si->next = svclist;svclist = si;}ALOGI("add_service('%s'), handle = %d\n", str8(s, len), handle);//向binder驱动发送BC_ACQUIREbinder_acquire(bs, handle);//向binder驱动发送BC_REQUEST_DEATH_NOTIFICATIONbinder_link_to_death(bs, handle, &si->death);return 0;
}struct svcinfo *find_svc(const uint16_t *s16, size_t len)
{struct svcinfo *si;for (si = svclist; si; si = si->next) {if ((len == si->len) && // 长度相同!memcmp(s16, si->name, len * sizeof(uint16_t))) { // s16和si->name内容相同// 如果svclist中已经包含添加的服务,则返回该服务svcinforeturn si;}}return NULL;
}void binder_acquire(struct binder_state *bs, uint32_t target)
{uint32_t cmd[2];cmd[0] = BC_ACQUIRE;cmd[1] = target;binder_write(bs, cmd, sizeof(cmd));
}void binder_link_to_death(struct binder_state *bs, uint32_t target, struct binder_death *death)
{struct {uint32_t cmd;struct binder_handle_cookie payload;} __attribute__((packed)) data;data.cmd = BC_REQUEST_DEATH_NOTIFICATION;data.payload.handle = target;data.payload.cookie = (uintptr_t) death;binder_write(bs, &data, sizeof(data));
}
2.2.2 获取服务

客户端向service_manager获取服务时发送的code值是SVC_MGR_CHECK_SERVICE,这在上面的源码分析中可知。

int svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{......case SVC_MGR_CHECK_SERVICE:// 获取服务的名字s = bio_get_string16(msg, &len);if (s == NULL) {return -1;}// 获取服务handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid);if (!handle)break;bio_put_ref(reply, handle);return 0;......
}uint32_t do_find_service(struct binder_state *bs, const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{struct svcinfo *si;if (!svc_can_find(s, len, spid)) {ALOGE("find_service('%s') uid=%d - PERMISSION DENIED\n",str8(s, len), uid);return 0;}// 从svclist中拿到服务名为s的svcinfo,这个函数在上面已经分析,这里不再赘述si = find_svc(s, len);//ALOGI("check_service('%s') handle = %x\n", str8(s, len), si ? si->handle : 0);// 拿到siif (si && si->handle) {if (!si->allow_isolated) {// If this service doesn't allow access from isolated processes,// then check the uid to see if it is isolated.uid_t appid = uid % AID_USER;if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {return 0;}}// 返回服务的handlereturn si->handle;} else {return 0;}
}

相关文章:

深入内核讲明白Android Binder【一】

深入内核讲明白Android Binder【一】 前言一、Android Binder应用编写概述二、基于C语言编写Android Binder跨进程通信Demo0. Demo简介1. 服务的管理者server_manager.c2. Binder服务端代码实现 test_service.c2.1 实现思路2.2 完整实现代码 3. Binder客户端代码实现 test_clie…...

Photoshop(PS)——人像磨皮

1.新建一个文件&#xff0c;背景为白色&#xff0c;将图片素材放入文件中 2.利用CtrlJ 复制两个图层出来&#xff0c;选择第一个拷贝图层&#xff0c;选择滤镜---杂色---蒙尘与划痕 3.调整一下数值&#xff0c;大概能够模糊痘印痘坑&#xff0c;点击确定。 4.然后选择拷贝2图层…...

如何用Excel批量提取文件夹内所有文件名?两种简单方法推荐

在日常办公中&#xff0c;我们有时需要将文件夹中的所有文件名整理在Excel表格中&#xff0c;方便管理和查阅。手动复制文件名既费时又易出错&#xff0c;因此本文将介绍两种利用Excel自动提取文件夹中所有文件名的方法&#xff0c;帮助你快速整理文件信息。 方法一&#xff1…...

YOLOv8改进,YOLOv8通过RFAConv卷积创新空间注意力和标准卷积,包括RFCAConv, RFCBAMConv,二次创新C2f结构,助力涨点

摘要 空间注意力已广泛应用于提升卷积神经网络(CNN)的性能,但它存在一定的局限性。作者提出了一个新的视角,认为空间注意力机制本质上解决了卷积核参数共享的问题。然而,空间注意力生成的注意力图信息对于大尺寸卷积核来说是不足够的。因此,提出了一种新型的注意力机制—…...

【实验11】卷积神经网络(2)-基于LeNet实现手写体数字识别

&#x1f449;&#x1f3fc;目录&#x1f448;&#x1f3fc; &#x1f352;1. 数据 1.1 准备数据 1.2 数据预处理 &#x1f352;2. 模型构建 2.1 模型测试 2.2 测试网络运算速度 2.3 输出模型参数量 2.4 输出模型计算量 &#x1f352;3. 模型训练 &#x1f352;4.模…...

chatgpt训练需要什么样的gpu硬件

训练像ChatGPT这样的大型语言模型对GPU硬件提出了极高的要求&#xff0c;因为这类模型的训练过程涉及大量的计算和数据处理。以下是训练ChatGPT所需的GPU硬件的关键要素&#xff1a; ### 1. **高性能计算能力** - **Tensor Cores**: 现代深度学习训练依赖于Tensor Cores&#…...

Kubernetes常用命令

Kubernetes常用命令 一、集群管理 kubectl cluster-info&#xff1a;显示集群信息&#xff0c;包括控制平面地址和服务的 URL。 kubectl get nodes&#xff1a;查看集群中的节点列表&#xff0c;包括节点状态、IP 地址等信息。 kubectl describe node <node-name>&…...

Flutter:key的作用原理(LocalKey ,GlobalKey)

第一段代码实现的内容&#xff1a;创建了3个块&#xff0c;随机3个颜色&#xff0c;每次点击按钮时&#xff0c;把第一个块删除 import dart:math; import package:flutter/material.dart; import package:flutter_one/demo.dart;void main() {runApp(const App()); }class App…...

R语言基础入门详解

文章目录 R语言基础入门详解一、引言二、R语言环境搭建1、安装R和RStudio1.1、步骤1.2、获取工作目录 三、R语言基础2、语法基础2.1、赋值操作2.2、注释 3、数据类型与结构3.1、向量3.2、矩阵 4、基本操作4.1、数据读取4.2、数据可视化 四、R语言使用示例4.1、统计分析示例4.2、…...

django启动项目报错解决办法

在启动此项目报错&#xff1a; 类似于&#xff1a; django.core.exceptions.ImproperlyConfigured: Requested setting EMOJI_IMG_TAG, but settings are not c启动方式选择django方式启动&#xff0c;以普通python方式启动会报错 2. 这句话提供了对遇到的错误的一个重要线索…...

详细描述一下Elasticsearch搜索的过程?

大家好&#xff0c;我是锋哥。今天分享关于【详细描述一下Elasticsearch搜索的过程&#xff1f;】面试题。希望对大家有帮助&#xff1b; 详细描述一下Elasticsearch搜索的过程&#xff1f; Elasticsearch 的搜索过程是其核心功能之一&#xff0c;允许用户对存储在 Elasticsea…...

Spring、SpringMVC、SpringBoot、Mybatis小结

Spring Spring是一个轻量级的控制反转&#xff08;IoC&#xff09;和面向切面&#xff08;AOP&#xff09;的容器&#xff08;框架&#xff09; Spring框架的核心特性包括依赖注入&#xff08;Dependency Injection &#xff0c;DI&#xff09;、面向切面编程&#xff08;Aspe…...

.NET 9 运行时中的新增功能

本文介绍了适用于 .NET 9 的 .NET 运行时中的新功能和性能改进。 文章目录 一、支持修剪的功能开关的属性模型二、UnsafeAccessorAttribute 支持泛型参数三、垃圾回收四、控制流实施技术.NET 安装搜索行为性能改进循环优化感应变量加宽Arm64 上的索引后寻址强度降低循环计数器可…...

Linux下安装mysql8.0版本

先确定我的下载安装的目录,安装文件是下载在 /opt/install 目录下面 (安装地址不同的话注意修改地址) 1.在线下载 wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz2.解压 tar -xvf mysql-8.0.20-linux-glibc2.12-x86_64.t…...

kvm-dmesg:从宿主机窥探虚拟机内核dmesg日志

在虚拟化环境中&#xff0c;实时获取虚拟机内核日志对于系统管理员和开发者来说至关重要。传统的 dmesg 工具可以方便地查看本地系统的内核日志&#xff0c;但在KVM&#xff08;基于内核的虚拟机&#xff09;环境下&#xff0c;获取虚拟机内部的内核日志则复杂得多。为了简化这…...

植物明星大乱斗15

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录 player.hplayer.cppparticle.hparticle.cpp player.h #pragma once #include <graphics.h> #include "vector2.h" #include "animation.h" #include "playerID.h" #include &…...

go-zero(三) 数据库操作

go-zero 数据库操作 在本篇文章中&#xff0c;我们将实现一个用户注册和登录的服务。我们将为此构建一个简单而高效的 API&#xff0c;包括请求参数和响应参数的定义。 一、Mysql连接 1. 创建数据库和表 在 MySQL 中创建名为 test_zero的数据库&#xff0c;并创建user 表 …...

SQL面试题——间隔连续问题

间隔连续问题 某游戏公司记录的用户每日登录数据如下 +----+----------+ | id| date| +----+----------+ |1001|2021-12-12| |1001|2021-12-13| |1001|2021-12-14| |1001|2021-12-16| |1001|2021-12-19| |1001|2021-12-20| |1002|2021-12-12| |1002|2021-12-16| |1002|…...

vim配置 --> 在创建的普通用户下

在目录/etc/ 下面&#xff0c;有个名为vimrc 的文件&#xff0c;这是系统中公共的vim配置文件对所有用户都有效 我们现在创建一个普通用户 dm 创建好以后&#xff0c;我们退出重新链接 再切换到普通用户下 再输入密码&#xff08;是不显示的&#xff0c;输入完后&#xff0c;…...

(计算机毕设)基于SpringBoot+Vue的房屋租赁系统的设计与实现

博主可接毕设设计&#xff01;&#xff01;&#xff01; 各种毕业设计源码只要是你有的题目我这里都有源码 摘 要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。互…...

【含开题报告+文档+PPT+源码】基于SpringBoot的医院药房管理系统

开题报告 在科技迅速发展的今天&#xff0c;各行各业都在积极寻求与现代技术的融合&#xff0c;以提升自身的运营效率和竞争力。医疗行业作为关乎国计民生的关键领域&#xff0c;其信息化建设的步伐尤为迅速。医院药房作为医疗体系中的核心环节&#xff0c;其管理效率和服务质…...

基于SpringBoot的“数码论坛系统设计与实现”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“数码论坛系统设计与实现”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面图 数码板…...

Linux-第2集-打包压缩 zip、tar WindowsLinux互传

欢迎来到Linux第2集&#xff0c;这一集我会非常详细的说明如何在Linux上进行打包压缩操作&#xff0c;以及解压解包 还有最最重要的压缩包的网络传输 毕竟打包压缩不是目的&#xff0c;把文件最终传到指定位置才是目的 由于打包压缩分开讲没有意义&#xff0c;并且它们俩本来…...

项目进度计划表:详细的甘特图的制作步骤

甘特图&#xff08;Gantt chart&#xff09;&#xff0c;又称为横道图、条状图&#xff08;Bar chart&#xff09;&#xff0c;是一种用于管理时间和任务活动的工具。 甘特图由亨利劳伦斯甘特&#xff08;Henry Laurence Gantt&#xff09;发明&#xff0c;是一种通过条状图来…...

Cargo Rust 的包管理器

Cargo->Rust 的包管理器 Cargi简介Cargo 的主要功能1. 创建项目2. 管理依赖3. 构建项目4. 运行项目5. 测试代码6. 检查代码7. 生成文档8. 发布和分享包 Cargo 的核心文件1. Cargo.toml2. Cargo.lock **Cargo 的生态系统** 常用命令总结Hello, Cargo! 示例 Cargi简介 Cargo …...

【Rust 编程语言工具】rustup-init.exe 安装与使用指南

rustup-init.exe 是用于安装和管理 Rust 编程语言工具链的 Windows 可执行文件。Rust 是一种系统级编程语言&#xff0c;旨在提供安全、并发和高性能的功能。rustup-init.exe 是官方提供的安装器&#xff0c;用于将 Rust 安装到 Windows 操作系统中&#xff0c;并配置相关环境。…...

集群聊天服务器(12)nginx负载均衡器

目录 负载均衡器nginx负载均衡器优势 如何解决集群聊天服务器跨服务器通信问题&#xff1f;nginx的TCP负载均衡配置nginx配置 负载均衡器 目前最多只能支持2w台客户机进行同时聊天 所以要引入集群&#xff0c;多服务器。 但是客户连哪一台服务器呢&#xff1f;客户并不知道哪一…...

数据挖掘英语及概念

分类 classify 上涨或跌 回归 regression 描述具体数值 分类模型评估 1.混淆&#xff08;误差&#xff09;矩阵 confusion matrix 2.ROC曲线 receiver operating characteristic curve 接收者操作特征曲线 3.AUC面积 area under curve ROC曲线下与坐标轴围成的面积&#x…...

springboot第82集:消息队列kafka,kafka-map

官网下载链接&#xff1a;https://kafka.[apache].org/downloads 我下载的是[Scala]2.12 - kafka_2.12-3.1.0.tgz kafka只需要解压下载的压缩包就行了&#xff0c;我这里解压的路径是D:\kafka_2.12-3.1.0&#xff0c;kafka的运行需要依赖zookeeper&#xff0c;当前版本已经内置…...

sql server查看当前正在执行的sql

#统计某类sql执行次数&#xff0c;并按总体cpu消耗时间降序排序 with a as ( select er.session_id,db_name(er.database_id) as DBNAME,sy.last_batch AS 最后执行时间, er.cpu_time ,er.total_elapsed_time/1000 as sum_elapsed_time_s, CAST(csql.text AS varchar(8000)) A…...

.net做网站之前设置/找个免费网站这么难吗

字符串相关 格式化字符串&#xff0c;可以使用String类的format(String,Object…)方法&#xff0c;如果要格式化资源文件strings.xml中的字符串&#xff0c;可以使用getResources().getString(int,Object…)方法 String.format("money:&#xffe5;%.2f",1.00); …...

如何自己做摄影网站/实时军事热点

Java运行时的数据区包括&#xff1a;&#xff08;其中前两个是线程共享的&#xff09; 1.方法区&#xff08;Method Area&#xff09; 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 2.堆&#xff08;Heap&#xff09; 存放对象实例&#xff0c;…...

企业网站打不开的原因/整合网络营销是什么

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼我:Baksmaling…我:加载资源表……我:加载。我:解码AndroidManifest。xml资源……从文件加载资源表:? / apktool /框架/ 1. apk我:加载。W:不能解码attr值,使用undecoded值:ns android,名称主题,值 0 x080d0018W:不能解码attr值,…...

b2b电商网站建设/品牌广告语

引言 前一篇文章中讲解了 Web Deploy 技术的简单使用&#xff0c;以及避免已有的 ACL 设置被清除的办法。 而这一次我将会讲解在使用 Visual Studio (Express) 进行一键发布时自动完成 ACL 设置的办法。 原理 在解决上一篇文章的问题的过程中中提到了 MSBuild 指令的使用&#…...

二级域名网站可以做关键词优化吗/今日广东头条新闻

Emacs v25.1 在win7 卡顿得厉害&#xff0c;滚动条拖动一下&#xff0c;就卡半天没反应&#xff0c;就像中了病毒一样。 解决&#xff1a; 打开菜单Options->Set Default Font&#xff0c;将字体改为宋体。 然后再回到Options菜单&#xff0c;点Save Options。这一步可千…...

美食网站建设规划书/百度一下官方网页

Oracle Grant详解GRANT 名称 GRANT — 赋予一个用户&#xff0c;一个组或所有用户访问权限 GRANT privilege [, ...] ON object [, ...] TO { PUBLIC | GROUP group | username } 输入 privilege 可能的权限有&#xff1a; SELECT 访问声明的表/视图的所有列/字段&#xff0…...