Spice VDI接口工作方式

作者“達沃時代” 原文鏈接:http://www.cnblogs.com/D-Tec/archive/2013/03/01/2939311.html

VDI接口工作方式

Spice中的VDI(Virtual Device Interfaces)是一個經過專門設計的接口規範,其設計哲學可以推廣到很多需要做功能擴展的軟件項目中。此類接口設計的主要目標是在儘量不改變原有代碼的情況下,通過動態庫的方式來爲原有軟件提供全新的功能組件。這種設計哲學在以開源代碼爲基礎的商業軟件開發中,其價值尤爲明顯,動態庫的代碼是完全私有的,因此可以不公開代碼。

具體到VDI,其設計目標是提供一個標準方法,能夠將虛擬設備的接口通過軟件組件直接開放給其他軟件組件使用。這裏提到了兩次“軟件組件”,是VDI接口標準的兩大組成部分,前者被稱作back-end,後者被稱作front-end。在Spice中,虛擬設備即爲back-end,如虛擬顯卡、虛擬聲卡等,LibSpice則充當front-end角色:

                       

VDI接口規範本身非常簡單,它僅爲開發人員提供一種標準的開發方式,具體的VDI接口功能則由程序員自己負責。具體的約束總結如下:

1、必須包含一個固定的BaseInterface結構體成員。

2、Back-end與Front-end的交互關係初始化工作由Back-end負責發起。

3、Back-end負責實現與Back-end內部強相關且Front-end感興趣的功能接口。

4、Front-end負責實現與Front-end內部強相關且Back-end感興趣的功能接口。

所有的約束條件都很容易理解:

約束1的目的是確保所有VDI可以共用統一的接口,BaseInterface相當於基類功能。

約束2的原因則是由於Front-end通常以動態庫的方式提供,屬於被動執行體。

約束3和4則純粹是軟件設計的必然。

VDI接口規範除了規定了BaseInterface以外,沒有規定具體的接口名稱和數量,由程序員自己自由發揮。即:程序員只需要按照上述約束分別實現Back-end和Front-end,並將兩邊的接口在初始化時告知對方即可。

Spice實現的VDI接口主要有:

SpiceCoreInterface:    Spice與Qemu內部交互用的接口

QXLInterface:          顯示數據交互接口

SpiceKbdInstance:      鍵盤輸入交互接口

SpiceMouseInterface:   鼠標輸入交互接口

SpicePlaybackInterface:音頻播放交互接口

SpiceRecordInterface:  音頻錄製交互接口

一、Qemu虛擬設備實現及初始化方法

VDI的Back-end是虛擬設備,因此有必要先簡要介紹Qemu的虛擬設備實現方法。Qemu實現了非常多的設備類型,常見的有PCI、ISA、IDE、I2C等,每種設備類型都以一個struct來表示,如:struct PCIDeviceInfo定義了一個PCI設備的具體內容。儘管每種設備的功能接口各不相同,但所有設備類型都必須實現一個init函數,該函數供Qemu來初始化此設備。

Qemu內部維護了一個全局的設備列表,所有Qemu內部實現的虛擬設備結構對象如顯卡、聲卡等都註冊到該全局設備列表中。Qemu的main函數執行以前,該全局設備列表已經初始化完成,因此在Qmeu的main函數執行過程中是找不到設備列表初始化的語句的,該特性必須由gcc編譯器支持。Qemu的main函數負責所有註冊到全局設備列表中的設備的初始化工作,即調用這些設備對象的init函數。

虛擬設備需要實現對應的物理設備所支持的所有基本功能,以虛擬顯卡爲例,虛擬設備必須實現標準VGA支持的所有I/0端口、顯存、ROM等,所有這些工作都必須在init中完成,以保證設備能正常工作。

二、SpiceCoreInterface

每個VDI接口都有對應的Back-end和Front-end,Spice中所有的Back-end都是虛擬設備,CoreInterface的Back-end也不例外,唯一不同的是CoreInterface實現的是一個假的虛擬設備,即:它以虛擬設備的形式實現,以使Qemu在初始化時可以以統一的方式調用init函數來初始化VDI的Back-end,但不往全局設備列表中註冊設備,不具備任何設備功能。

在介紹CoreInterface前先看看BaseInterface的定義:

struct SpiceBaseInterface {

    const char *type;

    const char *description;

    uint32_t major_version;

    uint32_t minor_version;

};

接下來是CoreInterface定義如下:

struct SpiceCoreInterface {

    SpiceBaseInterface base;

 

    SpiceTimer *(*timer_add)(SpiceTimerFunc func, void *opaque);

    void (*timer_start)(SpiceTimer *timer, uint32_t ms);

    void (*timer_cancel)(SpiceTimer *timer);

    void (*timer_remove)(SpiceTimer *timer);

 

    SpiceWatch *(*watch_add)(int fd, int event_mask, SpiceWatchFunc func, void *opaque);

    void (*watch_update_mask)(SpiceWatch *watch, int event_mask);

    void (*watch_remove)(SpiceWatch *watch);

 

    void (*channel_event)(int event, SpiceChannelEventInfo *info);

};

    BaseInterface只定義了最基本的幾個信息字段,用以描述此VDI接口的基本信息。另外代碼中有很多地方直接將BaseInterface類型的指針指針轉換爲具體的VDI類型,其依據就是每個VDI的第一個數據成員爲BaseInterface對象,類似這種轉換非常多。

    CoreInterface最主要的工作是定義了2組函數:Timer和Watch,其具體功能由Qemu實現,供libspice調用。這兩組函數的作用分別是用來在Qemu中設置定時器和維護網絡事件。下面以Watch函數組來詳細介紹CoreInterface的工作方式。

    Back-end:

    Qemu中會定義SpiceCoreInterface的一個靜態全局實例:

static SpiceCoreInterface core_interface = {

    .base.type          = SPICE_INTERFACE_CORE,

    .base.description   = "qemu core services",

    .base.major_version = SPICE_INTERFACE_CORE_MAJOR,

    .base.minor_version = SPICE_INTERFACE_CORE_MINOR,

 

    .timer_add          = timer_add,

    .timer_start        = timer_start,

    .timer_cancel       = timer_cancel,

    .timer_remove       = timer_remove,

 

    .watch_add          = watch_add,

    .watch_update_mask  = watch_update_mask,

    .watch_remove       = watch_remove,

 

#if SPICE_INTERFACE_CORE_MINOR >= 3

    .channel_event      = channel_event,

#endif

};

以上定義並初始化了一個SpiceCoreInterface實例,所有的數據成員和函數指針都做了初始化,其中就包括我們關心的Watch函數組:watch_add、watch_update_mask、watch_remove。core_interface是一個全局靜態數據對象,這一點屬於編碼細節的關鍵。Libspice是以動態庫的方式加載到Qemu的進程空間中的,因此可以將Qemu的全局數據對象直接給libspice使用。

與設備對象的初始化類似,利用gcc編譯器的特性,在main函數執行之前,Qemu會調用qemu_spice_init函數來執行CoreInterface的初始化,該函數內部會調用libspice對外的幾個有限的接口之一:spice_server_init函數來執行具體的CoreInterface初始化工作。

Front-end:

spice_server_init函數在libspice中實現,實際工作之一就是將Back-end傳遞過來的core_interface對象的地址賦值給在libspice中定義的一個全局SpiceCoreInterface指針core。這樣,libspice就可以使用CoreInterface中的各個回調函數了。

VDI中的成員函數都是以函數指針方式定義的,因此在編碼及編譯過程中,libspice都不需要實際的函數體,只需要在運行時正確的初始化這些函數指針就可以了。

對於CoreInterface來說,Back-end不需要Front-end提供服務,因此只在Qemu端爲libspice初始化了響應的接口。

以上完成了CoreInterface的定義和初始化,下面來看Watch接口如何被使用的:

spice_server_init初始化完core指針後,立即會調用reds_init_net來初始化spice的網絡監聽服務,實現函數如下:

static void reds_init_net()

{

    if (spice_port != -1) {

        reds->listen_socket = reds_init_socket();

        reds->listen_watch = core->watch_add(reds->listen_socket,

                                             SPICE_WATCH_EVENT_READ,

                                             reds_accept, NULL);

        if (reds->listen_watch == NULL) {

            red_error("set fd handle failed");

        }

    }

}

上面函數中調用core指針指向的watch_add函數來在Qemu增加一個socket事件。watch_add由Qemu實現,具體工作就是在一個socket隊列中增加一個新網絡socket,並註冊其對應的網絡事件處理函數reds_accept。當此socket可讀或可寫時,Qemu便調用reds_accept函數來處理網絡事件。第二個參數則定義了此socket關心的網絡事件,SPICE_WATCH_EVENT_READ表明當前socket只關心輸入數據,因爲該socket是負責網絡監聽的。

Qemu中針對Spice的網絡處理是移植的關鍵,因此CoreInterface中Watch函數組的工作機制相對來說是一個框架性的東西,且具有分非常好的靈活性。

三、QXLInterface

QXLInterface是最主要也是最複雜的一個VDI,並且與其他VDI不同的是,Front-end有一個單獨的網絡處理模型, QXLInterface在Front-end有自己單獨的處理線程。Back-end端需要實現Qxl device,同時需要Guest OS的Driver配合工作。另外,Qemu需要Spice爲其提供相關的接口來完成一些交互工作。

但是單從VDI接口本身來看,QXLInterface還是比較簡單的,複雜性全部隱藏在各自模塊內部。QXLInterface定義如下:

struct QXLInterface {

    SpiceBaseInterface base;

 

    void (*attache_worker)(QXLInstance *qin, QXLWorker *qxl_worker);

    void (*set_compression_level)(QXLInstance *qin, int level);

    void (*set_mm_time)(QXLInstance *qin, uint32_t mm_time);

 

    void (*get_init_info)(QXLInstance *qin, QXLDevInitInfo *info);

    int (*get_command)(QXLInstance *qin, struct QXLCommandExt *cmd);

    int (*req_cmd_notification)(QXLInstance *qin);

    void (*release_resource)(QXLInstance *qin, struct QXLReleaseInfoExt release_info);

    int (*get_cursor_command)(QXLInstance *qin, struct QXLCommandExt *cmd);

    int (*req_cursor_notification)(QXLInstance *qin);

    void (*notify_update)(QXLInstance *qin, uint32_t update_id);

    int (*flush_resources)(QXLInstance *qin);

};

    以上結構中的函數指針由Qemu實現,並在調用spice_server_add_interface時初始化並傳遞給libspice。所有函數均與視頻處理緊密相關,最重要的命令:get_command,用來從虛擬顯卡設備中獲取顯示處理命令。

    上面是Back-end提供給Front-end的接口,對於顯示處理,Front-end還需要爲Back-end提供接口,以使Back-end可以訪問Front-end,該接口通過如下數據結構封裝:

struct QXLWorker {

    uint32_t minor_version;

    uint32_t major_version;

    void (*wakeup)(QXLWorker *worker);

    void (*oom)(QXLWorker *worker);

    void (*start)(QXLWorker *worker);

    void (*stop)(QXLWorker *worker);

    void (*update_area)(QXLWorker *qxl_worker, uint32_t surface_id,

                       struct QXLRect *area, struct QXLRect *dirty_rects,

                       uint32_t num_dirty_rects, uint32_t clear_dirty_region);

    void (*add_memslot)(QXLWorker *worker, QXLDevMemSlot *slot);

    void (*del_memslot)(QXLWorker *worker, uint32_t slot_group_id, uint32_t slot_id);

    void (*reset_memslots)(QXLWorker *worker);

    void (*destroy_surfaces)(QXLWorker *worker);

    void (*destroy_primary_surface)(QXLWorker *worker, uint32_t surface_id);

    void (*create_primary_surface)(QXLWorker *worker, uint32_t surface_id,

                                   QXLDevSurfaceCreate *surface);

    void (*reset_image_cache)(QXLWorker *worker);

    void (*reset_cursor)(QXLWorker *worker);

    void (*destroy_surface_wait)(QXLWorker *worker, uint32_t surface_id);

    void (*loadvm_commands)(QXLWorker *worker, struct QXLCommandExt *ext, uint32_t count);

};

    當libspice收到Qemu的QXLInterface接口增加消息後,需要回填此結構給Qemu。顯示處理在libspice中是以單獨的線程進行工作的,因此上面的接口中有一部分就是用來控制線程運轉的命令,另一部分則是內存數據交互、圖像處理相關的控制接口。

   

四、SpiceKbdInstance、SpiceMouseInterface

鍵盤鼠標的VDI接口非常簡單:

struct SpiceKbdInterface {

    SpiceBaseInterface base;

 

    void (*push_scan_freg)(SpiceKbdInstance *sin, uint8_t frag);

    uint8_t (*get_leds)(SpiceKbdInstance *sin);

};

struct SpiceMouseInterface {

    SpiceBaseInterface base;

 

    void (*motion)(SpiceMouseInstance *sin, int dx, int dy, int dz,

                   uint32_t buttons_state);

    void (*buttons)(SpiceMouseInstance *sin, uint32_t buttons_state);

};

五、SpicePlaybackInterface、SpiceRecordInterface

聲音處理的兩個VDI接口實現方式則自成體系,代碼處理風格上與上面略有不同,先看這兩個VDI接口的聲明:

struct SpicePlaybackInterface {

    SpiceBaseInterface base;

};

struct SpiceRecordInterface {

    SpiceBaseInterface base;

};

什麼也沒有,只是封裝了一下BaseInterface!

這與音頻數據處理的實現方式有關係。虛擬顯卡產生視頻數據後,libspice需要主動調用get_command函數去獲取數據,然後在libspice中進行相關的處理後發送到client。而音頻處理則是在虛擬聲卡的實現中直接調用libspice導出的api,將音頻數據發送到client。因此Qemu不需要爲libspice開放接口。

音頻處理API接口如下:

spice_server_playback_put_samples

spice_server_record_get_samples

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章