首页 文章 精选 留言 我的

精选列表

搜索[虚拟线程],共10000篇文章
优秀的个人博客,低调大师

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

一、概述 本篇和第二篇是强相关的,需要结合第二篇一起看。 以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; } }

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册