Android系統(tǒng)模擬器繪制實現(xiàn)概述

什么是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)流程圖

整體實現(xiàn)流程圖
  • 系統(tǒng)到模擬器
系統(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
image.png

模擬器接收到指令協(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ù)的讀寫最終調用了RenderChannelreadFromGuestwriteToGuest障簿,其提供了一個Buffer來方便進行數(shù)據(jù)的讀寫站故。

  • RenderChannel

RenderChannel中的數(shù)據(jù)從哪里而來呢?跟進其幾個讀寫方法西篓,我們便會發(fā)現(xiàn)岂津,其具體的執(zhí)行是交給了mFromGuestmToGuest,其類型分別為

 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建立

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市惠奸,隨后出現(xiàn)的幾起案子梅誓,更是在濱河造成了極大的恐慌,老刑警劉巖佛南,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嵌言,居然都是意外死亡嗅回,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門摧茴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绵载,“玉大人,你說我怎么就攤上這事⊥薇” “怎么了焚虱?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懂版。 經(jīng)常有香客問我鹃栽,道長,這世上最難降的妖魔是什么躯畴? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任民鼓,我火速辦了婚禮,結果婚禮上蓬抄,老公的妹妹穿的比我還像新娘丰嘉。我一直安慰自己,他們只是感情好嚷缭,可當我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布饮亏。 她就那樣靜靜地躺著,像睡著了一般阅爽。 火紅的嫁衣襯著肌膚如雪克滴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天优床,我揣著相機與錄音劝赔,去河邊找鬼。 笑死胆敞,一個胖子當著我的面吹牛着帽,可吹牛的內容都是我干的。 我是一名探鬼主播移层,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼仍翰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了观话?” 一聲冷哼從身側響起予借,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎频蛔,沒想到半個月后灵迫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡晦溪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年瀑粥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片三圆。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡狞换,死狀恐怖避咆,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情修噪,我是刑警寧澤查库,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站黄琼,受9級特大地震影響樊销,放射性物質發(fā)生泄漏。R本人自食惡果不足惜适荣,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一现柠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弛矛,春花似錦够吩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至万俗,卻和暖如春湾笛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闰歪。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工嚎研, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人库倘。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓临扮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親教翩。 傳聞我的和親對象是個殘疾皇子杆勇,可洞房花燭夜當晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內容