什么是QEMU
QEMU是一套模擬處理器的開源軟件踊东。它與Bochs贱勃,PearPC近似,但其具有某些后兩者所不具備的特性蜀细,如高速度及跨平臺的特性珊皿。QEMU能模擬整個電腦系統(tǒng),包括中央處理器及其他周邊設備缎脾。它使得為系統(tǒng)源代碼進行測試及除錯工作變得容易。其亦能用來在一部主機上虛擬數(shù)部不同虛擬電腦奕谭。
Google在開發(fā)Android系統(tǒng)的同時,使用qemu開發(fā)了針對每個版本的一個模擬器痴荐,這大大降低了開發(fā)人員的開發(fā)成本血柳,便于Android技術的推廣。Google使用qemu模擬的是ARM926ej-S的Goldfish處理器生兆,Goldfish是一種虛擬的ARM處理器难捌,在Android的仿真環(huán)境中使用。Android模擬器通過運行它來運行arm926t指令集鸦难。
在Android源碼的device文件下根吁,我們可以看到有各個廠商的名稱,還有一個generic目錄合蔽,上面提到Android中goldfish為我們提供了對于底層硬件的虛擬化击敌,所以對于指令的執(zhí)行和硬件的操控,在程序執(zhí)行的時候都會轉交到這里來拴事,在該目錄下沃斤,可以看到有goldfish和goldfish-opengl,對于繪制相關的模擬在系統(tǒng)上層的調用中都會轉移到這里刃宵。例如OpenGLES和gralloc的相關調用衡瓶。在這里會對繪制指令進行編碼,通過HostConnection來進行數(shù)據(jù)的傳輸牲证,這里提供了一個HostConnetion類哮针,這里提供了兩種通信方式,一個是QemuPipe坦袍,一個是TcpStream的方式進行傳輸十厢。這里TcpStream實現(xiàn)存在問題,暫時不可使用键闺。到這里指令通過QemuPipe傳輸?shù)侥M器寿烟。模擬器再將接收到的指令轉化映射到相應的本地的繪制操作。
模擬器和Android系統(tǒng)繪制
整體實現(xiàn)流程圖
- 系統(tǒng)到模擬器
對于EGL辛燥,GLES1.1和GLES2.0的模擬這里會通過QEMU Pipe的方式傳輸?shù)侥M器筛武。在Android層中的實現(xiàn),通過將上層的指令轉化為一個通用的協(xié)議流挎塌,然后通過一個叫做QEMU PIPE
的高速通道來進行傳輸徘六,這個管道是通過內核驅動來實現(xiàn),提供了高速的帶寬榴都,可以非常高效的進行讀寫待锈。當數(shù)據(jù)通過流寫入到設備文件中,然后驅動從中拿到數(shù)據(jù)之后嘴高。繪制指令協(xié)議流被模擬器讀取之后竿音。
- 模擬器到RenderThread
模擬器接收到指令協(xié)議流之后并沒有做改變和屎,直接將指令導到Render相關類。
- Android模擬器的指令轉化
Android模擬器實現(xiàn)多個轉化的庫春瞬,實現(xiàn)了上層的EGL柴信,GLES。將相應的函數(shù)調用轉化為正確的宿主機的桌面API調用宽气。
GLX(Linux)随常,AGL(OS X),WGL(Windows)萄涯。OpenGL 2.0來模擬GLES1.1绪氛,GLES2.0.
Android系統(tǒng)到模擬器
在Goldfish-openGL下提供了對于EGL,GLES1.1涝影,GLES2.0的相應的編碼類枣察,對于其中實現(xiàn)的每一個方法獲取到當前gl_encoder_context持有的IOStream,來將數(shù)據(jù)寫入到流之中來進行通信袄琳。對于Android系統(tǒng)和模擬器之間的連接是通過HostConnection來實現(xiàn)的询件。其中的通信實現(xiàn)采用的是QemuPipe燃乍。
Android模擬器實現(xiàn)了一種特殊的虛擬設備類來提供宿主系統(tǒng)和模擬器之間非乘舴快速的通信渠道。該種通道的打開連接方式刻蟹。
首先打開/dev/qemu_pipe設備來進行讀和寫操作逗旁,從Linux3.10開始,設備被重新命名為/dev/goldfish_pipe舆瘪,但是和之前的操作還是一樣的片效。
提供一個零結尾的字符創(chuàng)描述我們所要連接的服務。
然后通過簡單的讀寫操作便可以和其進行通信英古。
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是要使用的服務名程淀衣,這里支持的服務有
- tcp:<port>
提供一個非內部模擬器的NAT router,我們只能使用這個socket進行讀寫召调,接受膨桥,不能夠進行連接非本地socket。
- unix:<path>
打開一個Unix域socket在主機上
- opengles
連接到OpenGL ES模擬進程唠叛,現(xiàn)在這個實現(xiàn)等于連接tcp:22468只嚣,但是未來可能會改變。
- qemud
連接到qemud服務在模擬器內艺沼,這個取代了老版本中通過/dev/ttys1的連接方式.
在內核中代碼册舞,向外提供了一個對于qemu_pipe,其中包含了我們如何與其進行交互障般。
由于QEMU Pipe發(fā)送數(shù)據(jù)的時候使用的是裸包调鲸,其速度要比TCP的方式快很多盛杰。
通信協(xié)議的實現(xiàn)
對于指令的傳輸,要對指令進行編解碼藐石。emugen饶唤,通過這個工具可以進行編碼解碼類的生成。在GLES1.1贯钩,GLES2.0募狂,EGL之中定義了一些代碼生成時,需要用到的文件角雷。用來定義生成代碼的文是.types祸穷,.in,.attrib勺三。對于EGL的聲明則是在renderControl雷滚。對于EGL的文件都是以‘renderControl’開頭的,這個主要是歷史原因吗坚,他們調用了gralloc系統(tǒng)的模塊來管理圖形緩沖區(qū)在比EGL更低的級別祈远。
EGL/GLES函數(shù)調用被通過一些規(guī)范文件進行描述,這些文件描述了類型商源,函數(shù)簽名和它們的一些屬性车份。系統(tǒng)的encoder靜態(tài)庫就是通過這些生成的文件來構建的,它們包含了可以將EGL/GLES命令轉化為簡單的byte信息的通過IOStream進行發(fā)送牡彻。
模擬器的繪制
模擬器接收渲染指令的位置扫沼,
在android/opengles.cpp控制了動態(tài)的裝載渲染庫,和正確的初始化庄吼,構建它缎除。host 渲染的庫在host/libs/libOpenglRender下,在模擬器opengles下的代碼掌管動態(tài)裝載一些渲染的庫总寻。
- RendererImpl
模擬器接收指令渲染的實現(xiàn)在RendererImpl中器罐,對于每一個新來的渲染client,都會通過createRenderChannel來創(chuàng)建一個RenderChannel渐行,然后創(chuàng)建一個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相關創(chuàng)建代碼
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創(chuàng)建成功之后,調用了其start方法殊轴。進入死循環(huán)衰倦,從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;
}
}
解碼過程樊零,省略部分代碼。保留了核心處理代碼。
指令流的來源
上面的指令流處理的數(shù)據(jù)從ChannelStream中來獲取驻襟,這里從ChannelStream著手進行分析夺艰。
- ChannelStream
我們先來看一下我們的協(xié)議流數(shù)據(jù)從何處而來,從數(shù)據(jù)讀取翻譯過程可以看出是來自我們的 ChannelStream
沉衣,而ChannelStream又是對于Channle的包裝郁副。接下來看一下ChannelStream的實現(xiàn)。
可以看到其是對于RenderChannel的一個包裝存谎,同時有兩個Buffer肥隆。
- ChannelStream 實現(xiàn)自IOStream
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進行執(zhí)行吸占,RenderChannel負責在Guest和Host之間的協(xié)議數(shù)據(jù)通信矾屯,然后ChannleStream提供了一些buffer在對其封裝的基礎上问拘,更方便的獲取其中的數(shù)據(jù)惧所,同時由于繼承自IOStream下愈,也定義了其中的一些接口势似,更方便調用。對于數(shù)據(jù)的讀寫最終調用了RenderChannel
的readFromGuest
和writeToGuest
障簿,其提供了一個Buffer來方便進行數(shù)據(jù)的讀寫站故。
- RenderChannel
RenderChannel中的數(shù)據(jù)從哪里而來呢?跟進其幾個讀寫方法西篓,我們便會發(fā)現(xiàn)岂津,其具體的執(zhí)行是交給了mFromGuest
和mToGuest
,其類型分別為
BufferQueue mFromGuest;
BufferQueue mToGuest;
通過調用其push橱乱,pop方法仅醇,從中獲取數(shù)據(jù)析二,到此叶摄,我們可以再繼續(xù)跟進一下BufferQueue
的創(chuàng)建和實現(xiàn)蛤吓。
- BufferQueue
mFromGuest(kGuestToHostQueueCapacity, mLock),
mToGuest(kHostToGuestQueueCapacity, mLock)
BufferQueue模型是Renderchannel的一個先進先出的隊列会傲,Buffer實例可以被用在不同的線程之間淌山,其同步原理是在創(chuàng)建的時候泼疑,傳遞了一個鎖進去。其內部的buffer利用就是RenderChannel的buffer荷荤。對于隊列的一些基本操作進行了相應的鎖處理退渗。
BufferQueue(size_t capacity, android::base::Lock& lock)
: mCapacity(capacity), mBuffers(new Buffer[capacity]), mLock(lock) {}
這里只是簡單地傳遞數(shù)據(jù)蕴纳,確定buffer的大小,同時為其加鎖古毛。
對于Buffer的讀寫翻翩,這里提供了四個關鍵函數(shù)。
- tryWrite(Buffer&& buffer)
mFromGuest.tryPushLocked(std::move(buffer));
- tryRead(Buffer* buffer)
mToGuest.tryPopLocked(buffer);
- writeToGuest(Buffer&& buffer)
mToGuest.pushLocked(std::move(buffer));
- readFromGuest(Buffer* buffer, bool blocking)
mFromGuest.popLocked(buffer);
在服務器這一端体斩,我們用的到的只有兩個函數(shù)梭稚,這兩個函數(shù)也是在ChannelStream中做了封裝的,分別為
- commitBuffer(size_t size)
mChannel->writeToGuest(std::move(mWriteBuffer));
- readRaw(void* buf, size_t* inout_len)
mChannel->readFromGuest(&mReadBuffer, blocking);
通過write和read函數(shù)可以看出是對端在使用的絮吵,用來接收從我們的隊列之中讀數(shù)據(jù)。
由于Android模擬器端接受繪制渲染指令是通過Qemu Pipe來接收的蹬敲,所以最開始接收到數(shù)據(jù)的位置則是管道服務伴嗡,其實現(xiàn)在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也就是我們上面提到的用來進行指令轉化在本地平臺進行繪制的麻惶。然后創(chuàng)建一個EmuglPipe實例。
實例創(chuàng)建的構造函數(shù)
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函數(shù)窃蹋。
EmuglPipe提供了幾個函數(shù)onGuestClose卡啰,onGuestPoll,onGuestRecv,onGuestSend等對于Guest讀寫的回調,當有數(shù)據(jù)到來或者要寫回的時候調用警没,這個時候就會調用renderChannel來進行指令流的讀寫匈辱。
代碼文檔位置
設計文檔
- qemu/android/docs
- android-emugl/DESIGN
相關代碼
系統(tǒng)端
- system/GLESv1_enc -> encoder for GLES 1.1 commands
- system/GLESv2_enc -> encoder for GLES 2.0 commands
- system/renderControl_enc -> encoder for rendering control commands
- system/egl -> emulator-specific guest EGL library
- system/GLESv1 -> emulator-specific guest GLES 1.1 library
- system/gralloc -> emulator-specific gralloc module
- system/OpenglSystemCommon -> library of common routines
模擬器端
host/libs/GLESv1_dec -> decoder for GLES 1.1 commands
host/libs/GLESv2_dec -> decoder for GLES 2.0 commands
host/libs/renderControl_dec -> decoder for rendering control commands
host/libs/Translator/EGL -> translator for EGL commands
host/libs/Translator/GLES_CM -> translator for GLES 1.1 commands
host/libs/Translator/GLES_V2 -> translator for GLES 2.0 commands
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.
external/qemu/android/android-emu/android/opengl/openglEsPipe/ -- >Qemu Pipe數(shù)據(jù)接收Renderj建立