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