QEMU是一套模擬處理器的開源軟件。它與Bochs,PearPC近似,但其具備某些後二者所不具有的特性,如高速度及跨平臺的特性。QEMU能模擬整個電腦系統,包括中央處理器及其餘周邊設備。它使得爲系統源代碼進行測試及除錯工做變得容易。其亦能用來在一部主機上虛擬數部不一樣虛擬電腦。android
Google在開發Android系統的同時,使用qemu開發了針對每一個版本的一個模擬器,這大大下降了開發人員的開發成本,便於Android技術的推廣。Google使用qemu模擬的是ARM926ej-S的Goldfish處理器,Goldfish是一種虛擬的ARM處理器,在Android的仿真環境中使用。Android模擬器經過運行它來運行arm926t指令集。bash
在Android源碼的device文件下,咱們能夠看到有各個廠商的名稱,還有一個generic目錄,上面提到Android中goldfish爲咱們提供了對於底層硬件的虛擬化,因此對於指令的執行和硬件的操控,在程序執行的時候都會轉交到這裏來,在該目錄下,能夠看到有goldfish和goldfish-opengl,對於繪製相關的模擬在系統上層的調用中都會轉移到這裏。例如OpenGLES和gralloc的相關調用。在這裏會對繪製指令進行編碼,經過HostConnection來進行數據的傳輸,這裏提供了一個HostConnetion類,這裏提供了兩種通訊方式,一個是QemuPipe,一個是TcpStream的方式進行傳輸。這裏TcpStream實現存在問題,暫時不可以使用。到這裏指令經過QemuPipe傳輸到模擬器。模擬器再將接收到的指令轉化映射到相應的本地的繪製操做。服務器
總體實現流程圖socket
對於EGL,GLES1.1和GLES2.0的模擬這裏會經過QEMU Pipe的方式傳輸到模擬器。在Android層中的實現,經過將上層的指令轉化爲一個通用的協議流,而後經過一個叫作QEMU PIPE
的高速通道來進行傳輸,這個管道是經過內核驅動來實現,提供了高速的帶寬,能夠很是高效的進行讀寫。當數據經過流寫入到設備文件中,而後驅動從中拿到數據以後。繪製指令協議流被模擬器讀取以後。tcp
模擬器接收到指令協議流以後並無作改變,直接將指令導到Render相關類。函數
Android模擬器實現多個轉化的庫,實現了上層的EGL,GLES。將相應的函數調用轉化爲正確的宿主機的桌面API調用。
GLX(Linux),AGL(OS X),WGL(Windows)。OpenGL 2.0來模擬GLES1.1,GLES2.0.工具
在Goldfish-openGL下提供了對於EGL,GLES1.1,GLES2.0的相應的編碼類,對於其中實現的每個方法獲取到當前gl_encoder_context持有的IOStream,來將數據寫入到流之中來進行通訊。對於Android系統和模擬器之間的鏈接是經過HostConnection來實現的。其中的通訊實現採用的是QemuPipe。測試
Android模擬器實現了一種特殊的虛擬設備類來提供宿主系統和模擬器之間很是快速的通訊渠道。該種通道的打開鏈接方式。ui
首先打開/dev/qemu_pipe設備來進行讀和寫操做,從Linux3.10開始,設備被從新命名爲/dev/goldfish_pipe,可是和以前的操做仍是同樣的。this
提供一個零結尾的字符創描述咱們所要鏈接的服務。
而後經過簡單的讀寫操做即可以和其進行通訊。
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是要使用的服務名程,這裏支持的服務有
提供一個非內部模擬器的NAT router,咱們只能使用這個socket進行讀寫,接受,不可以進行鏈接非本地socket。
打開一個Unix域socket在主機上
鏈接到OpenGL ES模擬進程,如今這個實現等於鏈接tcp:22468,可是將來可能會改變。
鏈接到qemud服務在模擬器內,這個取代了老版本中經過/dev/ttys1的鏈接方式.
在內核中代碼,向外提供了一個對於qemu_pipe,其中包含了咱們如何與其進行交互。
因爲QEMU Pipe發送數據的時候使用的是裸包,其速度要比TCP的方式快不少。
對於指令的傳輸,要對指令進行編解碼。emugen,經過這個工具能夠進行編碼解碼類的生成。在GLES1.1,GLES2.0,EGL之中定義了一些代碼生成時,須要用到的文件。用來定義生成代碼的文是.types,.in,.attrib。對於EGL的聲明則是在renderControl。對於EGL的文件都是以‘renderControl’開頭的,這個主要是歷史緣由,他們調用了gralloc系統的模塊來管理圖形緩衝區在比EGL更低的級別。
EGL/GLES函數調用被經過一些規範文件進行描述,這些文件描述了類型,函數簽名和它們的一些屬性。系統的encoder靜態庫就是經過這些生成的文件來構建的,它們包含了能夠將EGL/GLES命令轉化爲簡單的byte信息的經過IOStream進行發送。
模擬器接收渲染指令的位置,
在android/opengles.cpp控制了動態的裝載渲染庫,和正確的初始化,構建它。host 渲染的庫在host/libs/libOpenglRender下,在模擬器opengles下的代碼掌管動態裝載一些渲染的庫。
模擬器接收指令渲染的實如今RendererImpl中,對於每個新來的渲染client,都會經過createRenderChannel來建立一個RenderChannel,而後建立一個RenderThread。
RenderChannelPtr RendererImpl::createRenderChannel() {
const auto channel = std::make_shared<RenderChannelImpl>();
std::unique_ptr<RenderThread> rt(RenderThread::create(
shared_from_this(), channel));
if (!rt->start()) {
fprintf(stderr, "Failed to start RenderThread\n");
return nullptr;
}
return channel;
}複製代碼
RenderThread相關建立代碼
std::unique_ptr<RenderThread> RenderThread::create(
std::weak_ptr<RendererImpl> renderer,
std::shared_ptr<RenderChannelImpl> channel) {
return std::unique_ptr<RenderThread>(
new RenderThread(renderer, channel));
}複製代碼
RenderThread::RenderThread(std::weak_ptr<RendererImpl> renderer,
std::shared_ptr<RenderChannelImpl> channel)
: emugl::Thread(android::base::ThreadFlags::MaskSignals, 2 * 1024 * 1024),
mChannel(channel), mRenderer(renderer) {}複製代碼
在RenderThread建立成功以後,調用了其start方法。進入死循環,從ChannelStream之中讀取指令流,而後對指令流進行decode操做。
ChannelStream stream(mChannel, RenderChannel::Buffer::KSmallSize);
while(1) {
initialize decoders
//初始化解碼部分
tInfo.m_glDec.initGL(gles1_dispatch_get_proc_func, NULL); tInfo.m_gl2Dec.initGL(gles2_dispatch_get_proc_func, NULL); initRenderControlContext(&tInfo.m_rcDec);
ReadBuffer readBuf(kStreamBufferSize);
const int stat = readBuf.getData(&stream, packetSize);
//嘗試經過GLES1解碼器來解碼指令流
size_t last = tInfo.m_glDec.decode( readBuf.buf(), readBuf.validData(), &stream, &checksumCalc);
if (last > 0) {
progress = true;
readBuf.consume(last);
}
//嘗試經過GLESV2的解碼器來進行指令流
last = tInfo.m_gl2Dec.decode(readBuf.buf(), readBuf.validData(),
&stream, &checksumCalc);
FrameBuffer::getFB()->unlockContextStructureRead();
if (last > 0) {
progress = true;
readBuf.consume(last);
}
//嘗試經過renderControl解碼器來進行指令流的解碼
last = tInfo.m_rcDec.decode(readBuf.buf(), readBuf.validData(),
&stream, &checksumCalc);
if (last > 0) {
readBuf.consume(last);
progress = true;
}
}複製代碼
解碼過程,省略部分代碼。保留了核心處理代碼。
上面的指令流處理的數據從ChannelStream中來獲取,這裏從ChannelStream着手進行分析。
咱們先來看一下咱們的協議流數據從何處而來,從數據讀取翻譯過程能夠看出是來自咱們的 ChannelStream
,而ChannelStream又是對於Channle的包裝。接下來看一下ChannelStream的實現。
能夠看到其是對於RenderChannel的一個包裝,同時有兩個Buffer。
class ChannelStream final : public IOStream
ChannelStream(std::shared_ptr<RenderChannelImpl> channel, size_t bufSize);複製代碼
聲明瞭如下變量
std::shared_ptr<RenderChannelImpl> mChannel;
RenderChannel::Buffer mWriteBuffer;
RenderChannel::Buffer mReadBuffer;複製代碼
ChannelStream是對於RenderChannel進行了一次包裝,對於具體的操做仍是交到RenderChannel進行執行,RenderChannel負責在Guest和Host之間的協議數據通訊,而後ChannleStream提供了一些buffer在對其封裝的基礎上,更方便的獲取其中的數據,同時因爲繼承自IOStream,也定義了其中的一些接口,更方便調用。對於數據的讀寫最終調用了RenderChannel
的readFromGuest
和writeToGuest
,其提供了一個Buffer來方便進行數據的讀寫。
RenderChannel中的數據從哪裏而來呢?跟進其幾個讀寫方法,咱們便會發現,其具體的執行是交給了mFromGuest
和mToGuest
,其類型分別爲
BufferQueue mFromGuest;
BufferQueue mToGuest;複製代碼
經過調用其push,pop方法,從中獲取數據,到此,咱們能夠再繼續跟進一下BufferQueue
的建立和實現。
mFromGuest(kGuestToHostQueueCapacity, mLock),
mToGuest(kHostToGuestQueueCapacity, mLock)複製代碼
BufferQueue模型是Renderchannel的一個先進先出的隊列,Buffer實例能夠被用在不一樣的線程之間,其同步原理是在建立的時候,傳遞了一個鎖進去。其內部的buffer利用就是RenderChannel的buffer。對於隊列的一些基本操做進行了相應的鎖處理。
BufferQueue(size_t capacity, android::base::Lock& lock)
: mCapacity(capacity), mBuffers(new Buffer[capacity]), mLock(lock) {}複製代碼
這裏只是簡單地傳遞數據,肯定buffer的大小,同時爲其加鎖。
對於Buffer的讀寫,這裏提供了四個關鍵函數。
mFromGuest.tryPushLocked(std::move(buffer));複製代碼
mToGuest.tryPopLocked(buffer);複製代碼
writeToGuest(Buffer&& buffer)
mToGuest.pushLocked(std::move(buffer));複製代碼
readFromGuest(Buffer* buffer, bool blocking)
mFromGuest.popLocked(buffer);複製代碼
在服務器這一端,咱們用的到的只有兩個函數,這兩個函數也是在ChannelStream中作了封裝的,分別爲
mChannel->writeToGuest(std::move(mWriteBuffer));複製代碼
mChannel->readFromGuest(&mReadBuffer, blocking);複製代碼
經過write和read函數能夠看出是對端在使用的,用來接收從咱們的隊列之中讀數據。因爲Android模擬器端接受繪製渲染指令是經過Qemu Pipe來接收的,因此最開始接收到數據的位置則是管道服務,其實如今EmuglPipe中,在OpenglEsPipe文件中。
auto renderer = android_getOpenglesRenderer();
if (!renderer) {
D("Trying to open the OpenGLES pipe without GPU emulation!");
return nullptr;
}
EmuglPipe* pipe = new EmuglPipe(mHwPipe, this, renderer);
if (!pipe->mIsWorking) {
delete pipe;
pipe = nullptr;
}
return pipe;複製代碼
獲取一個Renderer也就是咱們上面提到的用來進行指令轉化在本地平臺進行繪製的。而後建立一個EmuglPipe實例。
實例建立的構造函數
EmuglPipe(void* hwPipe, Service* service,
const emugl::RendererPtr& renderer)
: AndroidPipe(hwPipe, service) {
mChannel = renderer->createRenderChannel();
if (!mChannel) {
D("Failed to create an OpenGLES pipe channel!");
return;
}
mIsWorking = true;
mChannel->setEventCallback([this](RenderChannel::State events) {this->onChannelHostEvent(events);});
}複製代碼
到此回到了上面最初介紹的Render的createRenderChannel函數。
EmuglPipe提供了幾個函數onGuestClose,onGuestPoll,onGuestRecv,onGuestSend等對於Guest讀寫的回調,當有數據到來或者要寫回的時候調用,這個時候就會調用renderChannel來進行指令流的讀寫。
設計文檔
相關代碼
系統端
模擬器端
host/libs/Translator/GLcommon -> library of common translation routines
host/libs/libOpenglRender -> 渲染庫 (uses all host libs above)can be used by the 'renderer' program below, or directly linked into the emulator UI program.