android emulator虚拟设备之qemu pipe分析(三)

if (status < 0) {

PIPE_E(“Could not open pipe channel, error=%d\n”, status);

kfree(pipe);

return status;

}

/* All is done, save the pipe into the file’s private data field */

file->private_data = pipe;

return 0;

}

qemu_pipe_read和qemu_pipe_write都是使用qemu_pipe_read_write来实现的,注意access_ok和__get_user/__put_user对于用户空间指针的检测。具体的读写比较简单,就是操作IO寄存器而已,需要注意的是,如果是非阻塞方式,需要进行阻塞等待。

具体的方法就是往PIPE_REG_COMMAND里面写CMD_WAKE_ON_WRITE或者CMD_WAKE_ON_READ,然后调用wait_event_interruptible去等待!test_bit(wakeBit, &pipe->flags)。

当中断来临时,会检查每一个CHANNEL的PIPE_REG_WAKES寄存器,如果可读 or 可写 or 已关闭,中断函数中会清除pipe->flags中的对应的等待标志位,然后wait_event_interruptible等待结束。如果是qemu_pipe被关闭的情况,wait_event_interruptible等待结束之后,检查到错误状态并退出。

/* This function is used for both reading from and writing to a given

* pipe.

*/

static ssize_t qemu_pipe_read_write(struct file *filp, char __user *buffer,

size_t bufflen, int is_write)

{

unsigned long irq_flags;

struct qemu_pipe *pipe = filp->private_data;

struct qemu_pipe_dev *dev = pipe->dev;

const int cmd_offset = is_write ? 0
(CMD_READ_BUFFER - CMD_WRITE_BUFFER);

unsigned long address, address_end;

int ret = 0;

/* If the emulator already closed the pipe, no need to go further */

if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) {

PIPE_W(“(write=%d) already closed!\n”, is_write);

ret = -EIO;

goto out;

}

/* Null reads or writes succeeds */

if (unlikely(bufflen) == 0)

goto out;

/* Check the buffer range for access */

if (!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ,

buffer, bufflen)) {

ret = -EFAULT;

PIPE_W(“rw access_ok failed\n”);

goto out;

}

/* Serialize access to the pipe */

if (mutex_lock_interruptible(&pipe->lock)) {

PIPE_W(“(write=%d) interrupted!\n”, is_write);

return -ERESTARTSYS;

}

address = (unsigned long)(void *)buffer;

address_end = address + bufflen;

while (address < address_end) {

unsigned long  page_end = (address & PAGE_MASK) + PAGE_SIZE;

unsigned long  next     = page_end < address_end ? page_end
address_end;

unsigned long  avail    = next - address;

int status, wakeBit;

/* Ensure that the corresponding page is properly mapped */

if (is_write) {

char c;

/* Ensure that the page is mapped and readable */

if (__get_user(c, (char __user *)address)) {

PIPE_E(“read fault at address 0x%08x\n”,

(unsigned int)address);

if (!ret)

ret = -EFAULT;

break;

}

} else {

/* Ensure that the page is mapped and writable */

if (__put_user(0, (char __user *)address)) {

PIPE_E(“write fault at address 0x%08x\n”,

(unsigned int)address);

if (!ret)

ret = -EFAULT;

break;

}

}

/* Now, try to transfer the bytes in the current page */

spin_lock_irqsave(&dev->lock, irq_flags);

if (dev->aps == NULL || access_with_param(

dev, CMD_WRITE_BUFFER + cmd_offset, address, avail,

pipe, &status) < 0)

{

writel((unsigned long)pipe,

dev->base + PIPE_REG_CHANNEL);

writel(avail, dev->base + PIPE_REG_SIZE);

writel(address, dev->base + PIPE_REG_ADDRESS);

writel(CMD_WRITE_BUFFER + cmd_offset,

dev->base + PIPE_REG_COMMAND);

status = readl(dev->base + PIPE_REG_STATUS);

}

spin_unlock_irqrestore(&dev->lock, irq_flags);

if (status > 0) { /* Correct transfer */

ret += status;

address += status;

continue;

}

if (status == 0)  /* EOF */

break;

/* An error occured. If we already transfered stuff, just

* return with its count. We expect the next call to return

* an error code */

if (ret > 0)

break;

/* If the error is not PIPE_ERROR_AGAIN, or if we are not in

* non-blocking mode, just return the error code.

*/

if (status != PIPE_ERROR_AGAIN ||

(filp->f_flags & O_NONBLOCK) != 0) {

ret = qemu_pipe_error_convert(status);

break;

}

/* We will have to wait until more data/space is available.

* First, mark the pipe as waiting for a specific wake signal.

*/

wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ;

set_bit(wakeBit, &pipe->flags);

/* Tell the emulator we’re going to wait for a wake event */

spin_lock_irqsave(&dev->lock, irq_flags);

writel((unsigned long)pipe, dev->base + PIPE_REG_CHANNEL);

writel(CMD_WAKE_ON_WRITE + cmd_offset,

dev->base + PIPE_REG_COMMAND);

spin_unlock_irqrestore(&dev->lock, irq_flags);

/* Unlock the pipe, then wait for the wake signal */

mutex_unlock(&pipe->lock);

while (test_bit(wakeBit, &pipe->flags)) {

if (wait_event_interruptible(

pipe->wake_queue,

!test_bit(wakeBit, &pipe->flags))) {

ret = -ERESTARTSYS;

PIPE_W(“rw, wait_event error\n”);

goto out;

}

if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) {

ret = -EIO;

PIPE_W(“rw, pipe already closed\n”);

goto out;

}

}

/* Try to re-acquire the lock */

if (mutex_lock_interruptible(&pipe->lock)) {

ret = -ERESTARTSYS;

goto out;

}

/* Try the transfer again */

continue;

}

mutex_unlock(&pipe->lock);

out:

return ret;

}

static ssize_t qemu_pipe_read(struct file *filp, char __user *buffer,

size_t bufflen, loff_t *ppos)

{

return qemu_pipe_read_write(filp, buffer, bufflen, 0);

}

static ssize_t qemu_pipe_write(struct file *filp,

const char __user *buffer, size_t bufflen,

loff_t *ppos)

{

return qemu_pipe_read_write(filp, (char __user *)buffer, bufflen, 1);

}

qemu_pipe_poll,实现poll,select,epoll接口用的,没什么特殊的,标准实现方式

static unsigned int qemu_pipe_poll(struct file *filp, poll_table *wait)

{

struct qemu_pipe *pipe = filp->private_data;

struct qemu_pipe_dev *dev = pipe->dev;

unsigned long irq_flags;

unsigned int mask = 0;

int status;

mutex_lock(&pipe->lock);

poll_wait(filp, &pipe->wake_queue, wait);

spin_lock_irqsave(&dev->lock, irq_flags);

writel((unsigned long)pipe, dev->base + PIPE_REG_CHANNEL);

writel(CMD_POLL, dev->base + PIPE_REG_COMMAND);

status = readl(dev->base + PIPE_REG_STATUS);

spin_unlock_irqrestore(&dev->lock, irq_flags);

mutex_unlock(&pipe->lock);

if (status & PIPE_POLL_IN)

mask |= POLLIN | POLLRDNORM;

if (status & PIPE_POLL_OUT)

mask |= POLLOUT | POLLWRNORM;

if (status & PIPE_POLL_HUP)

mask |= POLLHUP;

if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))

mask |= POLLERR;

return mask;

}

qemu_pipe_interrupt,中断处理函数,循环处理每一个qemu_pipe,看看是否可读 or 可写 or 关闭了,然后唤醒对应的线程

static irqreturn_t qemu_pipe_interrupt(int irq, void *dev_id)

{

struct qemu_pipe_dev *dev = dev_id;

unsigned long irq_flags;

int count = 0;

/* We’re going to read from the emulator a list of (channel,flags)

* pairs corresponding to the wake events that occured on each

* blocked pipe (i.e. channel).

*/

spin_lock_irqsave(&dev->lock, irq_flags);

for (;😉 {

/* First read the channel, 0 means the end of the list */

struct qemu_pipe *pipe;

unsigned long wakes;

unsigned long channel = readl(dev->base + PIPE_REG_CHANNEL);

if (channel == 0)

break;

/* Convert channel to struct pipe pointer + read wake flags */

wakes = readl(dev->base + PIPE_REG_WAKES);

pipe  = (struct qemu_pipe *)(ptrdiff_t)channel;

/* check if pipe is still valid */

if ((pipe = radix_tree_lookup(&dev->pipes,

(unsigned long)pipe)) == NULL) {

PIPE_W(“interrupt for already closed pipe\n”);

break;

}

/* Did the emulator just closed a pipe? */

if (wakes & PIPE_WAKE_CLOSED) {

set_bit(BIT_CLOSED_ON_HOST, &pipe->flags);

wakes |= PIPE_WAKE_READ | PIPE_WAKE_WRITE;

}

if (wakes & PIPE_WAKE_READ)

clear_bit(BIT_WAKE_ON_READ, &pipe->flags);

if (wakes & PIPE_WAKE_WRITE)

clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags);

wake_up_interruptible(&pipe->wake_queue);

count++;

}

spin_unlock_irqrestore(&dev->lock, irq_flags);

return (count == 0) ? IRQ_NONE : IRQ_HANDLED;

}

setup_access_params_addr和access_with_param用于快速读写的,看不懂的可以跳过:

/* 0 on success */

static int setup_access_params_addr(struct qemu_pipe_dev *dev)

{

uint64_t paddr;

struct access_params *aps;

aps = kmalloc(sizeof(struct access_params), GFP_KERNEL);

if (!aps)

return -1;

paddr = __pa(aps);

writel((uint32_t)(paddr >> 32), dev->base + PIPE_REG_PARAMS_ADDR_HIGH);

writel((uint32_t)paddr, dev->base + PIPE_REG_PARAMS_ADDR_LOW);

if (!valid_batchbuffer_addr(dev, aps))

return -1;

dev->aps = aps;

return 0;

}

/* A value that will not be set by qemu emulator */

#define IMPOSSIBLE_BATCH_RESULT (0xdeadbeaf)

static int access_with_param(struct qemu_pipe_dev *dev, const int cmd,

unsigned long address, unsigned long avail,

struct qemu_pipe *pipe, int *status)

{

struct access_params *aps = dev->aps;

aps->result = IMPOSSIBLE_BATCH_RESULT;

aps->channel = (unsigned long)pipe;

aps->size = avail;

aps->address = address;

aps->cmd = cmd;

writel(cmd, dev->base + PIPE_REG_ACCESS_PARAMS);

/* If aps->result unchanged, then batch command failed */

if (aps->result == IMPOSSIBLE_BATCH_RESULT)

return -1;

*status = aps->result;

return 0;

}

**另外需要说明的是几种不同的地址:

1、guest os进程虚拟地址,用户空间的地址,内核想使用这种地址时,需要调用copy_from_user与copy_to_user去验证是否正确然后才能读写

2、guest os内核虚拟地址,3GB~4GB

3、guest os内核物理地址,经典情况下,就是内核虚拟地址减去一个偏移量(3GB),物理内存较大时,情况不同。在qemu中通过safe_get_phys_page_debug可以把guest os内核虚拟地址转为guest os内核物理地址

4、emulator所在虚拟空间地址,我们的host os中的用户空间地址,qemu可以操作的内存地址。guest os内核物理地址通过cpu_physical_memory_map后可以map为qemu所在的虚拟空间的地址,然后qemu可以去使用内核传递过来的内存。**

**三、虚拟设备

pipe虚拟设备的代码为:http://androidxref.com/5.1.0_r1/xref/external/qemu/hw/android/goldfish/pipe.c**

初始化代码为pipe_dev_init,没啥好说的,比battery的简单多了。最后有三个调试用的东西,可以不看:

/* initialize the trace device */

void pipe_dev_init(bool newDeviceNaming)

{

PipeDevice *s;

s = (PipeDevice *) g_malloc0(sizeof(*s));

s->dev.name = newDeviceNaming ? “goldfish_pipe” : “qemu_pipe”;

s->dev.id = -1;

s->dev.base = 0;       // will be allocated dynamically

s->dev.size = 0x2000;

s->dev.irq = 0;

s->dev.irq_count = 1;

goldfish_device_add(&s->dev, pipe_dev_readfn, pipe_dev_writefn, s);

register_savevm(NULL,

“goldfish_pipe”,

0,

GOLDFISH_PIPE_SAVE_VERSION,

goldfish_pipe_save,

goldfish_pipe_load,

s);

#if DEBUG_ZERO_PIPE

goldfish_pipe_add_type(“zero”, NULL, &zeroPipe_funcs);

#endif

#if DEBUG_PINGPONG_PIPE

goldfish_pipe_add_type(“pingpong”, NULL, &pingPongPipe_funcs);

#endif

#if DEBUG_THROTTLE_PIPE

goldfish_pipe_add_type(“throttle”, NULL, &throttlePipe_funcs);

#endif

}

读函数为pipe_dev_read,需要注意的是PIPE_REG_CHANNEL。

kernel中的中断处理函数每次读取PIPE_REG_CHANNEL时,模拟设备都会将dev->signaled_pipes链表上的一个CHANNEL返回,并设置PIPE_REG_WAKES寄存器,告知kernel中pipe的驱动程序可以唤醒哪一个CHANNEL上的读等待 or 写等待的线程。

dev->signaled_pipes时满足条件,等待被唤醒的pipe列表,里面的节点是在goldfish_pipe_wake函数中添加的。

当dev->signaled_pipes为NULL时,通过goldfish_device_set_irq(&dev->dev, 0, 0)清除中断请求位。

/* I/O read */

static uint32_t pipe_dev_read(void *opaque, hwaddr offset)

{

PipeDevice *dev = (PipeDevice *)opaque;

switch (offset) {

case PIPE_REG_STATUS:

DR(“%s: REG_STATUS status=%d (0x%x)”, __FUNCTION__, dev->status, dev->status);

return dev->status;

case PIPE_REG_CHANNEL:

if (dev->signaled_pipes != NULL) {

Pipe* pipe = dev->signaled_pipes;

DR(“%s: channel=0x%llx wanted=%d”, __FUNCTION__,

(unsigned long long)pipe->channel, pipe->wanted);

dev->wakes = pipe->wanted;

pipe->wanted = 0;

dev->signaled_pipes = pipe->next_waked;

pipe->next_waked = NULL;

if (dev->signaled_pipes == NULL) {

goldfish_device_set_irq(&dev->dev, 0, 0);

DD(“%s: lowering IRQ”, __FUNCTION__);

}

return (uint32_t)(pipe->channel & 0xFFFFFFFFUL);

}

DR(“%s: no signaled channels”, __FUNCTION__);

return 0;

case PIPE_REG_CHANNEL_HIGH:

if (dev->signaled_pipes != NULL) {

Pipe* pipe = dev->signaled_pipes;

DR(“%s: channel_high=0x%llx wanted=%d”, __FUNCTION__,

(unsigned long long)pipe->channel, pipe->wanted);

return (uint32_t)(pipe->channel >> 32);

}

DR(“%s: no signaled channels”, __FUNCTION__);

return 0;

case PIPE_REG_WAKES:

DR(“%s: wakes %d”, __FUNCTION__, dev->wakes);

return dev->wakes;

case PIPE_REG_PARAMS_ADDR_HIGH:

return (uint32_t)(dev->params_addr >> 32);

case PIPE_REG_PARAMS_ADDR_LOW:

return (uint32_t)(dev->params_addr & 0xFFFFFFFFUL);

default:

D(“%s: offset=%d (0x%x)\n”, __FUNCTION__, offset, offset);

}

return 0;

}

写函数为pipe_dev_write,如果是写PIPE_REG_COMMAND,有专门的子函数pipeDevice_doCommand处理,如果是写PIPE_REG_ACCESS_PARAMS,相当于batch操作,传递了多个寄存器的值,然后去执行读写操作。

static void pipe_dev_write(void *opaque, hwaddr offset, uint32_t value)

{

PipeDevice *s = (PipeDevice *)opaque;

switch (offset) {

case PIPE_REG_COMMAND:

DR(“%s: command=%d (0x%x)”, __FUNCTION__, value, value);

pipeDevice_doCommand(s, value);

break;

case PIPE_REG_SIZE:

DR(“%s: size=%d (0x%x)”, __FUNCTION__, value, value);

s->size = value;

break;

case PIPE_REG_ADDRESS:

DR(“%s: address=%d (0x%x)”, __FUNCTION__, value, value);

uint64_set_low(&s->address, value);

break;

case PIPE_REG_ADDRESS_HIGH:

DR(“%s: address_high=%d (0x%x)”, __FUNCTION__, value, value);

uint64_set_high(&s->address, value);

break;

case PIPE_REG_CHANNEL:

DR(“%s: channel=%d (0x%x)”, __FUNCTION__, value, value);

uint64_set_low(&s->channel, value);

break;

case PIPE_REG_CHANNEL_HIGH:

DR(“%s: channel_high=%d (0x%x)”, __FUNCTION__, value, value);

uint64_set_high(&s->channel, value);

break;

case PIPE_REG_PARAMS_ADDR_HIGH:

s->params_addr = (s->params_addr & ~(0xFFFFFFFFULL << 32) ) |

((uint64_t)value << 32);

break;

case PIPE_REG_PARAMS_ADDR_LOW:

s->params_addr = (s->params_addr & ~(0xFFFFFFFFULL) ) | value;

break;

case PIPE_REG_ACCESS_PARAMS:

{

struct access_params aps;

struct access_params_64 aps64;

uint32_t cmd;

/* Don’t touch aps.result if anything wrong */

if (s->params_addr == 0)

break;

if (goldfish_guest_is_64bit()) {

cpu_physical_memory_read(s->params_addr, (void*)&aps64,

sizeof(aps64));

} else {

cpu_physical_memory_read(s->params_addr, (void*)&aps,

sizeof(aps));

}

/* sync pipe device state from batch buffer */

if (goldfish_guest_is_64bit()) {

s->channel = aps64.channel;

s->size = aps64.size;

s->address = aps64.address;

cmd = aps64.cmd;

} else {

s->channel = aps.channel;

s->size = aps.size;

s->address = aps.address;

cmd = aps.cmd;

}

if ((cmd != PIPE_CMD_READ_BUFFER) && (cmd != PIPE_CMD_WRITE_BUFFER))

break;

pipeDevice_doCommand(s, cmd);

if (goldfish_guest_is_64bit()) {

aps64.result = s->status;

cpu_physical_memory_write(s->params_addr, (void*)&aps64,

sizeof(aps64));

} else {

aps.result = s->status;

cpu_physical_memory_write(s->params_addr, (void*)&aps,

sizeof(aps));

}

}

break;

default:

D(“%s: offset=%d (0x%x) value=%d (0x%x)\n”, __FUNCTION__, offset,

offset, value, value);

break;

}

}

pipeDevice_doCommand,打开,关闭,读,写,可读时唤醒,可写时唤醒。

需要注意的是:

1、在刚打开CHANNEL时,pipe->funcs函数指针指向pipeConnector_funcs,根据guest os第一次写入到/dev/qemu_pipe的内容,得到pipe service的名字以及args。

之后,pipe->funcs都将指向对应的pipe service中实现的函数。

2、使用safe_get_phys_page_debug将传递过来的guest os内核虚拟地址转为guest os内核物理地址,然后使用qemu_get_ram_ptr转为emulator进程的虚拟空间地址。

static void

pipeDevice_doCommand( PipeDevice* dev, uint32_t command )

{

Pipe** lookup = pipe_list_findp_channel(&dev->pipes, dev->channel);

Pipe*  pipe   = *lookup;

CPUOldState* env = cpu_single_env;

/* Check that we’re referring a known pipe channel */

if (command != PIPE_CMD_OPEN && pipe == NULL) {

dev->status = PIPE_ERROR_INVAL;

return;

}

/* If the pipe is closed by the host, return an error */

if (pipe != NULL && pipe->closed && command != PIPE_CMD_CLOSE) {

dev->status = PIPE_ERROR_IO;

return;

}

switch (command) {

case PIPE_CMD_OPEN:

DD(“%s: CMD_OPEN channel=0x%llx”, __FUNCTION__, (unsigned long long)dev->channel);

if (pipe != NULL) {

dev->status = PIPE_ERROR_INVAL;

break;

}

pipe = pipe_new(dev->channel, dev);

pipe->next = dev->pipes;

dev->pipes = pipe;

dev->status = 0;

break;

case PIPE_CMD_CLOSE:

DD(“%s: CMD_CLOSE channel=0x%llx”, __FUNCTION__, (unsigned long long)dev->channel);

/* Remove from device’s lists */

*lookup = pipe->next;

pipe->next = NULL;

pipe_list_remove_waked(&dev->signaled_pipes, pipe);

pipe_free(pipe);

break;

case PIPE_CMD_POLL:

dev->status = pipe->funcs->poll(pipe->opaque);

DD(“%s: CMD_POLL > status=%d”, __FUNCTION__, dev->status);

break;

case PIPE_CMD_READ_BUFFER: {

/* Translate virtual address into physical one, into emulator memory. */

GoldfishPipeBuffer  buffer;

target_ulong        address = dev->address;

target_ulong        page    = address & TARGET_PAGE_MASK;

hwaddr  phys;

phys = safe_get_phys_page_debug(ENV_GET_CPU(env), page);

#ifdef TARGET_X86_64

phys = phys & TARGET_PTE_MASK;

#endif

buffer.data = qemu_get_ram_ptr(phys) + (address - page);

buffer.size = dev->size;

dev->status = pipe->funcs->recvBuffers(pipe->opaque, &buffer, 1);

DD(“%s: CMD_READ_BUFFER channel=0x%llx address=0x%16llx size=%d > status=%d”,

__FUNCTION__, (unsigned long long)dev->channel, (unsigned long long)dev->address,

dev->size, dev->status);

break;

}

case PIPE_CMD_WRITE_BUFFER: {

/* Translate virtual address into physical one, into emulator memory. */

GoldfishPipeBuffer  buffer;

target_ulong        address = dev->address;

target_ulong        page    = address & TARGET_PAGE_MASK;

hwaddr  phys;

phys = safe_get_phys_page_debug(ENV_GET_CPU(env), page);

#ifdef TARGET_X86_64

phys = phys & TARGET_PTE_MASK;

#endif

buffer.data = qemu_get_ram_ptr(phys) + (address - page);

buffer.size = dev->size;

dev->status = pipe->funcs->sendBuffers(pipe->opaque, &buffer, 1);

DD(“%s: CMD_WRITE_BUFFER channel=0x%llx address=0x%16llx size=%d > status=%d”,

__FUNCTION__, (unsigned long long)dev->channel, (unsigned long long)dev->address,

dev->size, dev->status);

break;

}

case PIPE_CMD_WAKE_ON_READ:

DD(“%s: CMD_WAKE_ON_READ channel=0x%llx”, __FUNCTION__, (unsigned long long)dev->channel);

if ((pipe->wanted & PIPE_WAKE_READ) == 0) {

pipe->wanted |= PIPE_WAKE_READ;

pipe->funcs->wakeOn(pipe->opaque, pipe->wanted);

}

dev->status = 0;

break;

case PIPE_CMD_WAKE_ON_WRITE:

DD(“%s: CMD_WAKE_ON_WRITE channel=0x%llx”, __FUNCTION__, (unsigned long long)dev->channel);

if ((pipe->wanted & PIPE_WAKE_WRITE) == 0) {

pipe->wanted |= PIPE_WAKE_WRITE;

pipe->funcs->wakeOn(pipe->opaque, pipe->wanted);

}

dev->status = 0;

break;

default:

D(“%s: command=%d (0x%x)\n”, __FUNCTION__, command, command);

}

}

pipeDevice_doCommand中提到的pipeConnector_funcs函数数组,只有一个pipeConnector_sendBuffers有效,其他都是空壳

pipeConnector_sendBuffers用于guest os第一次往/dev/qemu_pipe中写数据,数据内容为pipe::,去寻找匹配的pipe service,然后调用其初始化函数,得到peer(第三篇中的QemudPipe,也是pipe service funcs中的参数opaque),然后设置pipe->funcs指向pipe service提供的funcs。

static int

pipeConnector_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers )

{

PipeConnector* pcon = opaque;

const GoldfishPipeBuffer*  buffers_limit = buffers + numBuffers;

int ret = 0;

DD(“%s: channel=0x%llx numBuffers=%d”, __FUNCTION__,

(unsigned long long)pcon->pipe->channel,

numBuffers);

while (buffers < buffers_limit) {

int  avail;

DD(“%s: buffer data (%3d bytes): ‘%.*s’”, __FUNCTION__,

buffers[0].size, buffers[0].size, buffers[0].data);

if (buffers[0].size == 0) {

buffers++;

continue;

}

avail = sizeof(pcon->buffer) - pcon->buffpos;

if (avail > buffers[0].size)

avail = buffers[0].size;

if (avail > 0) {

memcpy(pcon->buffer + pcon->buffpos, buffers[0].data, avail);

pcon->buffpos += avail;

ret += avail;

}

buffers++;

}

/* Now check that our buffer contains a zero-terminated string */

if (memchr(pcon->buffer, ‘\0’, pcon->buffpos) != NULL) {

/* Acceptable formats for the connection string are:

*

*   pipe:

*   pipe::

*/

char* pipeName;

char* pipeArgs;

D(“%s: connector: ‘%s’”, __FUNCTION__, pcon->buffer);

if (memcmp(pcon->buffer, “pipe:”, 5) != 0) {

/* Nope, we don’t handle these for now. */

D(“%s: Unknown pipe connection: ‘%s’”, __FUNCTION__, pcon->buffer);

return PIPE_ERROR_INVAL;

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

题外话

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

/* Nope, we don’t handle these for now. */

D(“%s: Unknown pipe connection: ‘%s’”, __FUNCTION__, pcon->buffer);

return PIPE_ERROR_INVAL;

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-9ovZqoPI-1712609274150)]

[外链图片转存中…(img-LeIfrKCJ-1712609274150)]

[外链图片转存中…(img-hK2RlDsn-1712609274150)]

[外链图片转存中…(img-8TAOcnfP-1712609274151)]

[外链图片转存中…(img-2y3WChIo-1712609274151)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

题外话

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-19KFTsET-1712609274151)]

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值