android emulator虚拟设备分析第三篇之pipe上的qemud service

简介: 一、概述 本篇和第二篇是强相关的,需要结合第二篇一起看。 以boot-properties为例,注意不需要看ANDROID-QEMUD.TXT,这个是和guest os中的qemud进行相关的,已废弃。

一、概述

本篇和第二篇是强相关的,需要结合第二篇一起看。

boot-properties为例,注意不需要看ANDROID-QEMUD.TXT,这个是和guest os中的qemud进行相关的,已废弃。

启动emulator时,有一个参数-prop <key>=<value>,用于向guest os中添加属性。


二、guest os中使用qemud service的方法

实现代码是:http://androidxref.com/5.1.0_r1/xref/device/generic/goldfish/qemu-props/qemu-props.c,用到了头文件:http://androidxref.com/5.1.0_r1/xref/hardware/libhardware/include/hardware/qemud.h


guest os中程序名为qemu-props,由/system/etc/init.goldfish.rc启动。
启动后循环几次,尝试打开boot-properties服务(qemud_fd = qemud_channel_open( "boot-properties" ))。
如果打开成功,发送list命令(qemud_channel_send(qemud_fd, "list", -1))给boot-properties。
然后在循环中读取启动emulator时通过-prop指定的属性(qemud_channel_recv(qemud_fd, temp, sizeof temp - 1))。
并设置guest os中的属性(property_set(temp, q))。
 
 
 
 
qemud_channel_open,先尝试打开/dev/qemu_pipe,写入pipe:qemud:boot-properties。
如果pipe方式失败,才会去通过socket和qemud进程通信,写入boot-properties,期待返回OK。
 
static __inline__ int
qemud_channel_open(const char*  name)
{
    int  fd;
    int  namelen = strlen(name);
    char answer[2];
    char pipe_name[256];

    /* First, try to connect to the pipe. */
    snprintf(pipe_name, sizeof(pipe_name), "qemud:%s", name);
    fd = qemu_pipe_open(pipe_name);
    if (fd < 0) {
        D("QEMUD pipe is not available for %s: %s", name, strerror(errno));
        /* If pipe is not available, connect to qemud control socket */
        fd = socket_local_client( "qemud",
                                  ANDROID_SOCKET_NAMESPACE_RESERVED,
                                  SOCK_STREAM );
        if (fd < 0) {
            D("no qemud control socket: %s", strerror(errno));
            return -1;
        }

        /* send service name to connect */
        if (qemud_fd_write(fd, name, namelen) != namelen) {
            D("can't send service name to qemud: %s",
               strerror(errno));
            close(fd);
            return -1;
        }

        /* read answer from daemon */
        if (qemud_fd_read(fd, answer, 2) != 2 ||
            answer[0] != 'O' || answer[1] != 'K') {
            D("cant' connect to %s service through qemud", name);
            close(fd);
            return -1;
        }
    }
    return fd;
}


qemud_channel_send和qemud_channel_recv是qemu-pipe和qemud所通用的,直接对fd进行读写,先读写4个字节,为size,然后读取具体的内容。
 
static __inline__ int
qemud_channel_send(int  fd, const void*  msg, int  msglen)
{
    char  header[5];

    if (msglen < 0)
        msglen = strlen((const char*)msg);

    if (msglen == 0)
        return 0;

    snprintf(header, sizeof header, "%04x", msglen);
    if (qemud_fd_write(fd, header, 4) != 4) {
        D("can't write qemud frame header: %s", strerror(errno));
        return -1;
    }

    if (qemud_fd_write(fd, msg, msglen) != msglen) {
        D("can4t write qemud frame payload: %s", strerror(errno));
        return -1;
    }
    return 0;
}

static __inline__ int
qemud_channel_recv(int  fd, void*  msg, int  msgsize)
{
    char  header[5];
    int   size, avail;

    if (qemud_fd_read(fd, header, 4) != 4) {
        D("can't read qemud frame header: %s", strerror(errno));
        return -1;
    }
    header[4] = 0;
    if (sscanf(header, "%04x", &size) != 1) {
        D("malformed qemud frame header: '%.*s'", 4, header);
        return -1;
    }
    if (size > msgsize)
        return -1;

    if (qemud_fd_read(fd, msg, size) != size) {
        D("can't read qemud frame payload: %s", strerror(errno));
        return -1;
    }
    return size;
}


三、注册新的qemud service

所有的qemud service都使用pipe:qemud这个pipe service,是它的子服务。如何去实现这种子服务呢?

emulator里面有两中结构体QemudService, QemudClient分别表示子服务,以及子服务的client。

QemudPipe和之前说的pipe类似,每次打开/dev/qemu_pipe时,kernel和emulator中都会产生一个pipe,对应一个CHANNEL,在guest os第一次通过/dev/qemu_pipe发送数据时,会创建一个QemudPipe,也就是peer,作为pipe:qemud funcs中的opaque。

pipeConnector_sendBuffers函数代码片段:

        Pipe* pipe = pcon->pipe;
        void* peer = svc->funcs.init(pipe->hwpipe, svc->opaque, pipeArgs);
        if (peer == NULL) {
            D("%s: Initialization failed for pipe %s!", __FUNCTION__, pipeName);
            return PIPE_ERROR_INVAL;
        }

        /* Do the evil switch now */
        pipe->opaque = peer;
        pipe->service = svc;
        pipe->funcs  = &svc->funcs;
        pipe->args   = ASTRDUP(pipeArgs);
        AFREE(pcon);



3.1、pipe:qemud服务

代码为external/qemu/android/emulation/android_qemud.cpp,我在android源码中没有找到,在另一个模拟器的repo中找到了。注意代码中夹杂着一些guest os中qemud相关的东西,关键词serial,不需要看。


初始化代码如下,_qemudPipe_funcs就是第二篇中所说的svc->funcs,从第二次通信开始,qemu_pipe都使用这些funcs去读写。

/* QEMUD pipe functions.
 */
static const AndroidPipeFuncs _qemudPipe_funcs = {
        _qemudPipe_init,
        _qemudPipe_closeFromGuest,
        _qemudPipe_sendBuffers,
        _qemudPipe_recvBuffers,
        _qemudPipe_poll,
        _qemudPipe_wakeOn,
        _qemudPipe_save,
        _qemudPipe_load,
};

/* Initializes QEMUD pipe interface.
 */
static void _android_qemud_pipe_init(void) {
    static bool _qemud_pipe_initialized = false;

    if (!_qemud_pipe_initialized) {
        android_pipe_add_type("qemud", looper_getForThread(), &_qemudPipe_funcs);
        _qemud_pipe_initialized = true;
    }
}

static bool isInited = false;

void android_qemud_init(CSerialLine* sl) {
    D("%s", __FUNCTION__);
    /* We don't know in advance whether the guest system supports qemud pipes,
     * so we will initialize both qemud machineries, the legacy (over serial
     * port), and the new one (over qemu pipe). Then we let the guest to connect
     * via one, or the other. */
    _android_qemud_serial_init(sl);
    _android_qemud_pipe_init();
    isInited = true;
}


_qemudPipe_init是建立连接后,初始化QemudPipe的代码。

QemudMultiplexer中只有两个链表有用。

先根据service name查找子服务QemudService,然后调用子服务的qemud_service_connect_client去创建QemudClient,然后去创建QemudPipe

/* This is a callback that gets invoked when guest is connecting to the service.
 *
 * Here we will create a new client as well as pipe descriptor representing new
 * connection.
 */
static void*
_qemudPipe_init(void* hwpipe, void* _looper, const char* args) {
    QemudMultiplexer* m = qemud_multiplexer;
    QemudService* sv = m->services;
    QemudClient* client;
    QemudPipe* pipe = NULL;
    char service_name[512];
    const char* client_args;
    size_t srv_name_len;

    /* 'args' passed in this callback represents name of the service the guest is
     * connecting to. It can't be NULL. */
    if (args == NULL) {
        D("%s: Missing address!", __FUNCTION__);
        return NULL;
    }

    /* 'args' contain service name, and optional parameters for the client that
     * is about to be created in this call. The parameters are separated from the
     * service name wit ':'. Separate service name from the client param. */
    client_args = strchr(args, ':');
    if (client_args != NULL) {
        srv_name_len = min(client_args - args, (intptr_t) sizeof(service_name) - 1);
        client_args++;  // Past the ':'
        if (*client_args == '\0') {
            /* No actual parameters. */
            client_args = NULL;
        }
    } else {
        srv_name_len = min(strlen(args), sizeof(service_name) - 1);
    }
    memcpy(service_name, args, srv_name_len);
    service_name[srv_name_len] = '\0';

    /* Lookup registered service by its name. */
    while (sv != NULL && strcmp(sv->name, service_name)) {
        sv = sv->next;
    }
    if (sv == NULL) {
        D("%s: Service '%s' has not been registered!", __FUNCTION__, service_name);
        return NULL;
    }

    /* Create a client for this connection. -1 as a channel ID signals that this
     * is a pipe client. */
    client = qemud_service_connect_client(sv, -1, client_args);
    if (client != NULL) {
        pipe = static_cast<QemudPipe*>(android_alloc0(sizeof(*pipe)));
        pipe->hwpipe = hwpipe;
        pipe->looper = _looper;
        pipe->service = sv;
        pipe->client = client;
        client->ProtocolSelector.Pipe.qemud_pipe = pipe;
    }

    return pipe;
}


_qemudPipe_sendBuffers是guest通过/dev/qemu_pipe写数据时,将被调用的函数,也就是QemudClient接收到数据的函数,注意不要把send/recv的概念搞错了。

代码就是把guest发送的buffers拼起来,然后调用QemudClient的接收函数qemud_client_recv去处理。

/* Called when the guest has sent some data to the client.
 */
static int
_qemudPipe_sendBuffers(void* opaque,
                       const AndroidPipeBuffer* buffers,
                       int numBuffers) {
    QemudPipe* pipe = static_cast<QemudPipe*>(opaque);
    QemudClient* client = pipe->client;
    size_t transferred = 0;

    if (client == NULL) {
        D("%s: Unexpected NULL client", __FUNCTION__);
        return -1;
    }

    if (numBuffers == 1) {
        /* Simple case: all data are in one buffer. */
        D("%s: %s", __FUNCTION__, quote_bytes((char*) buffers->data, buffers->size));
        qemud_client_recv(client, buffers->data, buffers->size);
        transferred = buffers->size;
    } else {
        /* If there are multiple buffers involved, collect all data in one buffer
         * before calling the high level client. */
        uint8_t* msg, * wrk;
        int n;
        for (n = 0; n < numBuffers; n++) {
            transferred += buffers[n].size;
        }
        msg = static_cast<uint8_t*>(malloc(transferred));
        wrk = msg;
        for (n = 0; n < numBuffers; n++) {
            memcpy(wrk, buffers[n].data, buffers[n].size);
            wrk += buffers[n].size;
        }
        D("%s: %s", __FUNCTION__, quote_bytes((char*) msg, transferred));
        qemud_client_recv(client, msg, transferred);
        free(msg);
    }

    return transferred;
}


_qemudPipe_recvBuffers是guest想从/dev/qemu_pipe读取数据时被调用的。

QemudClient写数据时是写到自己的ProtocolSelector.Pipe.messages中的,在这个函数中把QemudClient中的ProtocolSelector.Pipe.messages倒腾到buffers中。

/* Called when the guest is reading data from the client.
 */
static int
_qemudPipe_recvBuffers(void* opaque, AndroidPipeBuffer* buffers, int numBuffers) {
    QemudPipe* pipe = static_cast<QemudPipe*>(opaque);
    QemudClient* client = pipe->client;
    QemudPipeMessage** msg_list;
    AndroidPipeBuffer* buff = buffers;
    AndroidPipeBuffer* endbuff = buffers + numBuffers;
    size_t sent_bytes = 0;
    size_t off_in_buff = 0;

    if (client == NULL) {
        D("%s: Unexpected NULL client", __FUNCTION__);
        return -1;
    }

    msg_list = &client->ProtocolSelector.Pipe.messages;
    if (*msg_list == NULL) {
        /* No data to send. Let it block until we wake it up with
         * PIPE_WAKE_READ when service sends data to the client. */
        return PIPE_ERROR_AGAIN;
    }

    /* Fill in goldfish buffers while they are still available, and there are
     * messages in the client's message list. */
    while (buff != endbuff && *msg_list != NULL) {
        QemudPipeMessage* msg = *msg_list;
        /* Message data fiting the current pipe's buffer. */
        size_t to_copy = min(msg->size - msg->offset, buff->size - off_in_buff);
        memcpy(buff->data + off_in_buff, msg->message + msg->offset, to_copy);
        /* Update offsets. */
        off_in_buff += to_copy;
        msg->offset += to_copy;
        sent_bytes += to_copy;
        if (msg->size == msg->offset) {
            /* We're done with the current message. Go to the next one. */
            *msg_list = msg->next;
            free(msg);
        }
        if (off_in_buff == buff->size) {
            /* Current pipe buffer is full. Continue with the next one. */
            buff++;
            off_in_buff = 0;
        }
    }

    D("%s: -> %u (of %u)", __FUNCTION__, sent_bytes, buffers->size);

    return sent_bytes;
}


_qemudPipe_poll,PIPE_POLL_OUT总是有效,PIPE_POLL_IN需要看QemudClient的ProtocolSelector.Pipe.messages中是否有数据

static unsigned
_qemudPipe_poll(void* opaque) {
    QemudPipe* pipe = static_cast<QemudPipe*>(opaque);
    QemudClient* client = pipe->client;
    unsigned ret = 0;

    if (client != NULL) {
        ret |= PIPE_POLL_OUT;
        if (client->ProtocolSelector.Pipe.messages != NULL) {
            ret |= PIPE_POLL_IN;
        }
    } else {
        D("%s: Unexpected NULL client", __FUNCTION__);
    }

    return ret;
}


_qemudPipe_wakeOn,发现ProtocolSelector.Pipe.messages中有数据时,会调用android_pipe_wake,把pipe添加到dev->signaled链表中。

static void
_qemudPipe_wakeOn(void* opaque, int flags) {
    QemudPipe* qemud_pipe = (QemudPipe*) opaque;
    QemudClient* c = qemud_pipe->client;
    D("%s: -> %X", __FUNCTION__, flags);
    if (flags & PIPE_WAKE_READ) {
        if (c->ProtocolSelector.Pipe.messages != NULL) {
            android_pipe_wake(c->ProtocolSelector.Pipe.qemud_pipe->hwpipe,
                              PIPE_WAKE_READ);
        }
    }
}



3.2、qemud service

代码是external/qemu/android/boot-properties.c,也是在模拟器repo中的


boot_property_init_service去注册一个QemudService,主要函数就一个boot_property_service_connect,用于创建新的QemudClient

void
boot_property_init_service( void )
{
    if (!_inited) {
        QemudService*  serv = qemud_service_register( SERVICE_NAME,
                                                      1, NULL,
                                                      boot_property_service_connect,
                                                      boot_property_save,
                                                      boot_property_load);
        if (serv == NULL) {
            derror("could not register '%s' service", SERVICE_NAME);
            return;
        }
        D("registered '%s' qemud service", SERVICE_NAME);

        _inited = 1;
    }
}


boot_property_service_connect创建新的QemudClient,channel一般都是-1,表示是pipe方式,而不是serial方式(使用guest qemud进程)

static QemudClient*
boot_property_service_connect( void*          opaque,
                               QemudService*  serv,
                               int            channel,
                               const char*    client_param )
{
    QemudClient*  client;

    client = qemud_client_new( serv, channel, client_param, NULL,
                               boot_property_client_recv,
                               NULL, NULL, NULL );

    qemud_client_set_framing(client, 1);
    return client;
}


qemud_client_new会绑定QemudClient的读写函数,读函数boot_property_client_recv(也就是qemud_client_recv)是在_qemudPipe_sendBuffers中调用的

循环执行qemud_client_send将数据(-prop指定的属性值的列表)写到QemudClient的ProtocolSelector.Pipe.messages中,当_qemudPipe_recvBuffers函数执行时,从QemudClient的ProtocolSelector.Pipe.messages中倒腾数据返回给guest

void
boot_property_client_recv( void*         opaque,
                           uint8_t*      msg,
                           int           msglen,
                           QemudClient*  client )
{
    /* the 'list' command shall send all boot properties
     * to the client, then close the connection.
     */
    if (msglen == 4 && !memcmp(msg, "list", 4)) {
        BootProperty*  prop;
        for (prop = _boot_properties; prop != NULL; prop = prop->next) {
            qemud_client_send(client, (uint8_t*)prop->property, prop->length);
        }

        /* Send a NUL to signal the end of the list. */
        qemud_client_send(client, (uint8_t*)"", 1);

        return;
    }

    /* unknown command ? */
    D("%s: ignoring unknown command: %.*s", __FUNCTION__, msglen, msg);
}




boot-properties服务的入口函数是boot_property_parse_option,emulator在解析-prop参数时,会调用这个函数。

获得name和value后,调用boot_property_add2(name, namelen, value, valuelen)去添加属性到属性列表(_boot_properties)中

void
boot_property_parse_option( const char*  param )
{
    char* q = strchr(param,'=');
    const char* name;
    const char* value;
    int   namelen, valuelen, ret;

    if (q == NULL) {
        dwarning("boot property missing (=) separator: %s", param);
        return;
    }

    name    = param;
    namelen = q - param;

    value    = q+1;
    valuelen = strlen(name) - (namelen+1);

    ret = boot_property_add2(name, namelen, value, valuelen);
    if (ret < 0) {
        boot_property_raise_warning(ret, name, namelen, value, valuelen);
    }
}



boot_property_add2会检查服务是否已初始化,如果没有,将调用boot_property_init_service。如果属性名和值没有非法字符,将申请新的属性:prop = boot_property_alloc(name, namelen, value, valuelen)并添加到属性列表中

/* Appends a new boot property to the end of the internal list.
 */
int
boot_property_add2( const char*  name, int  namelen,
                    const char*  value, int  valuelen )
{
    BootProperty*  prop;

    /* check the lengths
     */
    if (namelen > PROPERTY_MAX_NAME)
        return -1;

    if (valuelen > PROPERTY_MAX_VALUE)
        return -2;

    /* check that there are not invalid characters in the
     * property name
     */
    const char*  reject = " =$*?'\"";
    int          nn;

    for (nn = 0; nn < namelen; nn++) {
        if (strchr(reject, name[nn]) != NULL)
            return -3;
    }

    /* init the service */
    boot_property_init_service();

    /* add to the end of the internal list */
    prop = boot_property_alloc(name, namelen, value, valuelen);

    *_boot_properties_tail = prop;
    _boot_properties_tail  = &prop->next;

    return 0;
}




boot_property_init_service先检查是否已初始化,如果没有,将进行初始化
QemudService*  serv = qemud_service_register( SERVICE_NAME,
                                                       1, NULL,
                                                       boot_property_service_connect,
                                                       boot_property_save,
                                                       boot_property_load);
第二个参数是max_clients,最大客户数量
第三个参数是serv_opaque,将传递给注册的serv_connect函数的第一个参数
第四个参数是注册的serv_connect函数
第五、第六是保存和恢复属性链表的函数

void
boot_property_init_service( void )
{
    if (!_inited) {
        QemudService*  serv = qemud_service_register( SERVICE_NAME,
                                                      1, NULL,
                                                      boot_property_service_connect,
                                                      boot_property_save,
                                                      boot_property_load);
        if (serv == NULL) {
            derror("could not register '%s' service", SERVICE_NAME);
            return;
        }
        D("registered '%s' qemud service", SERVICE_NAME);

        _inited = 1;
    }
}



目录
相关文章
|
26天前
|
开发工具 Android开发 Swift
安卓与iOS开发环境对比分析
在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统无疑是主角。它们各自拥有独特的特点和优势,为开发者提供了不同的开发环境和工具。本文将深入浅出地探讨安卓和iOS开发环境的主要差异,包括开发工具、编程语言、用户界面设计、性能优化以及市场覆盖等方面,旨在帮助初学者更好地理解两大平台的开发特点,并为他们选择合适的开发路径提供参考。通过比较分析,我们将揭示不同环境下的开发实践,以及如何根据项目需求和目标受众来选择最合适的开发平台。
34 2
|
2月前
|
Shell Linux 开发工具
"开发者的救星:揭秘如何用adb神器征服Android设备,开启高效调试之旅!"
【8月更文挑战第20天】Android Debug Bridge (adb) 是 Android 开发者必备工具,用于实现计算机与 Android 设备间通讯,执行调试及命令操作。adb 提供了丰富的命令行接口,覆盖从基础设备管理到复杂系统操作的需求。本文详细介绍 adb 的安装配置流程,并列举实用命令示例,包括设备连接管理、应用安装调试、文件系统访问等基础功能,以及端口转发、日志查看等高级技巧。此外,还提供了常见问题的故障排除指南,帮助开发者快速解决问题。掌握 adb 将极大提升 Android 开发效率,助力项目顺利推进。
52 0
|
7天前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
|
14天前
|
安全 Linux Android开发
探索安卓与iOS的安全性差异:技术深度分析
本文深入探讨了安卓(Android)和iOS两个主流操作系统平台在安全性方面的不同之处。通过比较它们在架构设计、系统更新机制、应用程序生态和隐私保护策略等方面的差异,揭示了每个平台独特的安全优势及潜在风险。此外,文章还讨论了用户在使用这些设备时可以采取的一些最佳实践,以增强个人数据的安全。
|
1月前
|
IDE 开发工具 Android开发
安卓与iOS开发环境对比分析
本文将探讨安卓和iOS这两大移动操作系统在开发环境上的差异,从工具、语言、框架到生态系统等多个角度进行比较。我们将深入了解各自的优势和劣势,并尝试为开发者提供一些实用的建议,以帮助他们根据自己的需求选择最适合的开发平台。
28 1
|
2月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
本文介绍了如何在基于Amlogic T972的Android 9.0系统上使用Platform平台驱动框架和设备树(DTS),实现设备与驱动的分离,并通过静态枚举在设备树中描述设备,自动触发驱动程序的加载和设备创建。
18 0
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
|
2月前
|
Android开发 C语言
基于Amlogic 安卓9.0, 驱动简说(二):字符设备驱动,自动创建设备
这篇文章是关于如何在基于Amlogic T972的Android 9.0系统上,通过自动分配设备号和自动创建设备节点文件的方式,开发字符设备驱动程序的教程。
36 0
基于Amlogic 安卓9.0, 驱动简说(二):字符设备驱动,自动创建设备
|
2月前
|
自然语言处理 Shell Linux
基于Amlogic 安卓9.0, 驱动简说(一):字符设备驱动,手动创建设备
本文是关于在Amlogic安卓9.0平台上创建字符设备驱动的教程,详细介绍了驱动程序的编写、编译、部署和测试过程,并提供了完整的源码和应用层调用示例。
50 0
基于Amlogic 安卓9.0, 驱动简说(一):字符设备驱动,手动创建设备
|
2月前
|
传感器 Android开发 芯片
不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动
本文是系列文章的第三篇,展示了如何在Android系统中利用现有的i2c bus驱动,通过编写设备树节点和应用层的控制代码,实现对基于i2c bus的Slaver设备(如六轴陀螺仪模块QMI8658C)的控制,而无需编写设备驱动代码。
30 0
不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动
|
2月前
|
Android开发
不写一行代码(二):实现安卓基于PWM的LED设备驱动
本文介绍了在Android系统中不编写任何代码,通过设备树配置和内核支持的通用PWM LED驱动来实现基于PWM的LED设备驱动,并通过测试命令调整LED亮度级别。
34 0
不写一行代码(二):实现安卓基于PWM的LED设备驱动

推荐镜像

更多
下一篇
无影云桌面