Android QEMU 高速管道
介紹
Android 模擬器實現了一個特殊的虛擬設備,用于提供客戶 Android 系統和模擬器本身 非常 快速的通信通道。
在客戶 Android 系統端,用法非常簡單,如下:
1/ 打開 /dev/qemu_pipe 設備文件來讀和寫
注意:自 Linux 3.10 開始,設備被重命名為了 /dev/goldfish_pipe,但行為完全一樣。
2/ 寫入描述你想要連接的服務,且以 0 結束的字符串。
3/ 簡單地使用 read() 和 write() 來與服務通信。
換句話說:
fd = open("/dev/qemu_pipe", O_RDWR);const char* pipeName = "<pipename>";ret = write(fd, pipeName, strlen(pipeName)+1);if (ret < 0) {// error}... ready to go其中 <pipename> 是你想要使用的特定模擬器服務的名字。本文檔在后面列出了支持的模擬器服務的名字。
實現細節
在模擬器的源碼樹中:
./hw/android/goldfish/pipe.c 實現虛擬驅動。./hw/android/goldfish/pipe.h 提供了任何模擬器 pipe 服務必須提供的接口。./android/hw-pipe-net.c 包含網絡 pipe 服務(比如 'tcp' 和 'unix')的實現。更多詳情參考下文。模擬器根據所使用的 Android Linux 內核的不同,而有兩個不同的版本,即使用 goldfish 內核的 QEMU1 和使用 ranchu 內核的 QEMU2。上面列出的這些文件,是用于 QEMU1 的相關文件。這些文件在 emulator 2.3 release 版本代碼庫中的具體位置分別為 qemu/android/qemu1/hw/android/goldfish/pipe.c,qemu/android/qemu1/include/hw/android/goldfish/pipe.h,而 hw-pipe-net.c 文件則已經被移除了。關于網絡虛擬化的代碼,分布于 qemu/net/、qemu/hw/net/ 目錄下。
在內核源碼樹中:
drivers/misc/qemupipe/qemu_pipe.c 包含在客戶系統內,可通過 /dev/qemu_pipe訪問的驅動源碼。這文件當然是在模擬器版的 Linux 內核中,也就是從 git clone https://android.googlesource.com/kernel/goldfish clone 下來的代碼中,且在 android-goldfish-3.4 分支中。如在前面提到的,自 3.10 版內核開始,設備被重命名為了 /dev/goldfish_pipe,實現該設備的驅動程序文件也變為了 goldfish/drivers/platform/goldfish/goldfish_pipe.c
設備 / 驅動協議細節
設備和驅動使用一個 I/O 內存頁和一個 IRQ 來通信。
驅動寫入不同的 I/O 寄存器來向設備發送命令。
設備觸發一個 IRQ 通知驅動某一事件發生了。
驅動讀取 I/O 寄存器獲得它的最新命令的狀態,或者在中斷的情況下發生的事件的列表。
客戶系統內每個打開的 /dev/qemu_pipe 的文件描述符對應一個由驅動分配的 32 位的 ‘通道’ 值。
下面的內容描述了驅動向設備發送的不同命令。以 REG_ 開頭的可變的名字對應于 32 位的 I/O 寄存器:
0/ 通道和地址值
依賴于客戶系統的 CPU 架構,每個通信通道由一個非零的 32 位或 64 位值標識。
由內核向模擬器發送的通道值為:
void write_channel(channel) {#if 64BIT_GUEST_CPUREG_CHANNEL_HIGH = (channel >> 32);#endifREG_CHANNEL = (channel & 0xffffffffU);}類似地,當給模擬器傳遞一個內核地址時:
void write_address(buffer_address) {#if 64BIT_GUEST_CPUREG_ADDRESS_HIGH = (buffer_address >> 32);#endifREG_ADDRESS = (buffer_address & 0xffffffffU);}1/ 創建一個新的通道
驅動用來表示客戶系統打開了將由名稱 ‘’ 標識的 /dev/qemu_pipe:
write_channel(<channel>)REG_CMD = CMD_OPEN重要:<channel> 從不應該為 0
2/ 關閉一個通道
驅動用來表示客戶系統調用了通道文件描述符的 ‘close’。
write_channel(<channel>)REG_CMD = CMD_CLOSE3/ 向通道寫入數據
對應于客戶系統在通道的文件描述符上執行 write() 或 writev()。這個命令用于發送一個單獨的內存緩沖區:
write_channel(<channel>)write_address(<buffer-address>)REG_SIZE = <buffer-size>REG_CMD = CMD_WRITE_BUFFERstatus = REG_STATUS注意:<buffer-address> 是 客戶系統 的緩沖區地址,而不是物理的/內核的。
重要:通過這個命令發送的緩沖區 應該總是 被完整的包含在一個客戶系統的內存頁內。這是強制的,用以簡化驅動程序和設備。
如果一個 write() 跨越多個客戶系統的內存頁,驅動將連續觸發多個,對客戶端透明的 CMD_WRITE_BUFFER 命令。
REG_STATUS 返回的值應該是:
> 0 寫入 pipe 的字節數
0 表示流結束狀態
< 0 負值錯誤碼(參考下文)。
一個重要的錯誤碼是 PIPE_ERROR_AGAIN,用于表示寫操作還不能執行。參考 CMD_WAKE_ON_WRITE 了解更多內容。
4/ 從通道讀取數據
對應于客戶系統在通道的文件描述符上執行 read() 和 readv()。
write_channel(<channel>)write_address(<buffer-address>)REG_SIZE = <buffer-size>REG_CMD = CMD_READ_BUFFERstatus = REG_STATUS有著相同的緩沖區地址/長度限制和相同的錯誤碼集合。
5/ 等待寫能力
如果 CMD_WRITE_BUFFER 返回 PIPE_ERROR_AGAIN,且文件描述符不是處于非阻塞模式,驅動必須把客戶端任務放進一個等待隊列中,直到 pipe 服務可以再次接受數據。
在此之前,驅動將執行:
write_channel(<channel>)REG_CMD = CMD_WAKE_ON_WRITE以向虛擬設備表明,它正在等待,并應該在 pipe 再次變得可寫時被喚醒。稍后將解釋這是如何完成的。
6/ 等待讀能力
這與 CMD_WAKE_ON_WRITE 一樣,但要替換為讀能力。
write_channel(<channel>)REG_CMD = CMD_WAKE_ON_READ7/ 輪詢(polling)可讀/可寫狀態
下面的命令由驅動用于實現 select(),poll() 和 epoll() 系統調用,其中包含一個 pipe 通道。
write_channel(<channel>)REG_CMD = CMD_POLLmask = REG_STATUSREG_STATUS 返回的掩碼值是自上次調用以來,哪些事件可用/已經發生的位標記的混合。參考 PIPE_POLL_READ / PIPE_POLL_WRITE / PIPE_POLL_CLOSED。
8/ 向驅動通知事件
設備可以通過生成它的 IRQ 來向驅動通知事件。驅動的中斷處理器隨后將讀取一個以單獨的 0 值結束的通道的 (channel,mask) 對的列表。
換句話說,驅動的中斷處理器將執行:
for (;;) {channel = REG_CHANNELif (channel == 0) // END OF LISTbreak;mask = REG_WAKES // BIT FLAGS OF EVENTS... process events}通過這個列表報告的事件很簡單:
PIPE_WAKE_READ :: 通道現在可讀。 PIPE_WAKE_WRITE :: 通道現在可寫。 PIPE_WAKE_CLOSED :: pipe 服務關閉了連接如果 CMD_WAKE_ON_READ 或 CMD_WAKE_ON_WRITE(分別地)為給定通道發出,則 PIPE_WAKE_READ 和 PIPE_WAKE_WRITE 僅向它報告。
PIPE_WAKE_CLOSED 可以在任何時間通知。
9/ 通過參數塊的更快的讀/寫
最近的 Goldfish 內核實現了一種更快的方式來執行讀和寫,它每個操作執行一個單獨的 I/O 寫操作(當通過 KVM 或 HAX 模擬 x86 系統時很有用)。
它使用下面同時為虛擬設備和內核所知的結構,在 $QEMU/hw/android/goldfish/pipe.h 中定義:
32 位客戶系統 CPU 的是:
struct access_params {uint32_t channel;uint32_t size;uint32_t address;uint32_t cmd;uint32_t result;/* reserved for future extension */uint32_t flags;};64 位的是:
struct access_params_64 {uint64_t channel;uint32_t size;uint64_t address;uint32_t cmd;uint32_t result;/* reserved for future extension */uint32_t flags;};這是把多個參數打包進一個單獨的結構的簡單的方式。起初的,比如,在啟動時間,內核將分配一個這樣的結構,并這樣傳遞它的物理地址:
PARAMS_ADDR_LOW = (params & 0xffffffff);PARAMS_ADDR_HIGH = (params >> 32) & 0xffffffff;然后對于每個操作,它將做這樣一些事情:
params.channel = channel;params.address = buffer;params.size = buffer_size;params.cmd = CMD_WRITE_BUFFER (or CMD_READ_BUFFER)REG_ACCESS_PARAMS = <any>status = params.status向 REG_ACCESS_PARAMS 寫入將觸發操作,比如 QEMU 將讀取參數塊的內容,使用它的字段來執行操作,然后把返回值寫回 params.status。
10/ v2 pipe: 通過命令緩沖區和緩沖區列表的更快的讀/寫
批量 access_params 依然一次只能執行一個緩沖區傳輸。這對于每次傳輸只使用一個內存頁的應用來說是 OK 的,但對于許多延遲/吞吐量敏感的應用,比如 OpenGL 和 ADB push/pull,每次操作一個緩沖區/頁是不夠的。
理想情況下,如果客戶系統想要傳輸緩沖區,那么應該盡可能地像在一個步驟中完成。
但是,客戶系統的內存頁更可能是分散在多個位置,且不是物理上連續的,使得這變得很困難。
v2 Goldfish pipe 驅動和設備修改了寄存器集合以及 device/pipe 結構,以允許 goldfish_pipe_read_write 的每個循環傳輸更多的緩沖區,為所有吞吐量受限的 pipe 用戶提升性能。
有兩個關鍵的新結構要考慮。
對于每個 pipe fd,都有一個對應的 goldfish_pipe_command 結構,它可以持有最多 MAX_BUFFERS_PER_COMMAND (具體地說,目前為 10^2)個緩沖區。緩沖區中包含客戶系統內在 rw_params 中追蹤的數據。需要做些努力來使列表盡可能短(比如,合并物理上連續的頁等)。
以前,對于主機觸發的傳輸,我們遍歷具有掛起的 actions (PIPE_WAKE_***) 的 pipe 的鏈表,稱為 “signaled pipes” 列表,中斷處理器在每次傳輸發生時將粗略地處理 1-3 個這樣的 pipes。這不是很理想,因為 a) 我們在中斷處理器中執行任務和 b) 在客戶系統中運行內核驅動與在主機上運行虛擬設備之間可能有大量的事務(事務很慢)。
因此,pipe v2 記錄了一系列來自于主機 IRQ 觸發的要處理的 pipe:
且每個中斷處理器一次可以處理最多 MAX_SIGNALLED_PIPES 個。此外,喚醒被通知的 pipes 的工作被留給了一個 bottom-half 處理器,且不在中斷上下文中完成。
11/ goldfish_dma:從主機系統直接訪問客戶系統上的內存
有時,對于同時需要非常高的吞吐量及非常低的延時的應用,比如視頻回放,跳過主機/客戶事務及收集非連續的緩沖區,而直接寫入對主機系統也可見的內存可能是很有用的。
為了做到這一點,goldfish_dma 允許任何 pipe fd 用 ioctls 在內核空間分配一塊連續的物理區域,然后從客戶系統的用戶空間 mmap() 到那個物理區域。這些區域也可以跨進程共享,比如通過 gralloc。
goldfish_dma 內核驅動將 struct goldfish_dma_context 添加到所有的 struct goldfish_pipe 實例中:
struct goldfish_dma_context {struct kref kref; /* kref to safely free dma region */struct device* pdev_dev; /* pointer to feed to dma_***_coherent */void* dma_vaddr; /* kernel vaddr of dma region */dma_addr_t dma_bus_addr; /* kernel dma_addr_t of dma region */u64 dma_size; /* size of dma region */u64 phys_begin; /* paddr of dma region */unsigned long pfn; /* pfn of dma region */u64 phys_end; /* paddr of dma region + dma_size */bool locked; /* marks whether the region is currently in use */unsigned long refcount; /* For debugging purposes only. */struct goldfish_pipe_command* command_buffer; /* For safe freeing of DMA regions,we need another pipe command buffer that lives longer than the pipe instance */};Interface (guest side):客戶系統用戶在一個 goldfish pipe fd 上調用 goldfish_dma_alloc (ioctls) 以及 mmap(),這意味著它想要高速訪問主機可見的內存。
然后客戶系統可以寫入 mmap() 返回的指針,隨后這些寫入在主機上變得立即可見而無需 BQL 及上下為切換。
主要的數據結構跟蹤狀態是 struct goldfish_dma_context,它作為一個額外的指針成員而被包含在 struct goldfish_pipe 中。每個這樣的上下文可能與由一個物理地址和大小描述的分配的 DMA 區域關聯,且對于每個 pipe fd 只允許有一次分配。更多的分配需要 pipe fd 的更多 open()。
dma_alloc_coherent() 被用于獲得連續的物理內存區域,然后我們在客戶系統和主機系統上分配并與這一區域交互,通過如下的 ioctls:
- LOCK:為數據訪問鎖定 DMA 區域。
- UNLOCK:解鎖 DMA 區域。這也可能在主機系統上通過 WAKE_ON_UNLOCK_DMA 完成。
- CREATE_REGION:為一個 DMA 區域初始化大小信息。
- GETOFF:發送物理地址給客戶系統驅動。
(UN)MAPHOST:使用 goldfish_pipe_cmd 告訴主機系統,(un)map 與當前 dma 上下文關聯的客戶系統的物理地址。這使物理的連續內存對于主機系統 (不) 可見。
客戶系統用戶空間使用 mmap() 獲得一個指向 DMA 內存的指針,它也用 dma_alloc_coherent() 來延遲分配內存。(在最后一個 pipe close() 中,該區域被釋放)。
mmap() 映射的區域可以處理非常高帶寬的傳輸,且 pipe 操作可與處理同步和命令通信一起使用。
在內核驅動中虛擬設備有這些 hooks:
- UNLOCK_DMA:從主機系統中解鎖 DMA 區域。
- MAP/UNMAP_HOST:這是一個 pipe 命令,用于告訴主機獲得一個主機的 void* 指向特定的客戶系統 RAM 物理地址。當通過 ioctl() 在客戶系統的內核中分配一個新的 DMA 緩沖區時觸發,以使主機系統也可以看到相同的區域。
- UNLOCK_DMA:這個 pipe 命令通知客戶系統內核,主機系統完成了對 DMA 區域的處理。在大多數簡單的用戶案例中,客戶系統將首先鎖定它的 DMA 區域然后寫入它,假設處理完成后 unlock 將由主機系統觸發。UNLOCK_DMA 是主機如何觸發這種解鎖的方式。
然后主機系統將調用 cpu_physical_memory_(un)map 并在一個全局的數據結構 (DmaMap) 中追蹤所有當前分配的區域。如果我們拍快照,我們將需要 remap 那些區域,一些東西也由 DmaMap 處理。
可用的服務
tcp:
打開一個到給定 localhost 端口的 TCP socket。它提供了一個不依賴于非常慢的內部模擬器 NAT 路由器的非常快速的通道。注意,你只能以 read() 和 write() 使用文件描述符,send() 和 recv() 將返回一個 ENOTSOCK 錯誤,任何 socket 的 ioctl() 也是。
出于安全原因,無法連接非 localhost 端口。
unix:
打開一個主機上的 Unix 域 socket。
opengles
連接 OpenGL ES 模擬進程。目前為止,其實現等價于 tcp:22468,但在未來這可能會改變。
qemud
連接模擬器內部的 QEMUD 服務。它替代了在老的 Android 平臺發行版本上通過 /dev/ttyS1 執行的連接。詳情請參考 $QEMU/android/docs/ANDROID-QEMUD.TXT。
打賞
原文 —- emu-2.4-release/external/qemu/android/docs/ANDROID-QEMU-PIPE.TXT
Done.
總結
以上是生活随笔為你收集整理的Android QEMU 高速管道的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 将应用打包为 Snaps
- 下一篇: QEMU 网络虚拟化