天天看點

xen 添加網卡裝置初步分析之 network-attach 流程分析1          流程分析

本文從 xm network-attach 指令着手,逐漸分析xen 平台添加網卡的整體流程。需要說明的是,本文檔隻介紹針對在PV驅動環境下前後端網卡裝置的添加流程。對于非PV驅動下的全虛拟化裝置,不予關注。

1          流程分析

Xm network-attach 是添加網卡裝置的指令,該指令是用python語言編寫,相關源檔案位于主機/usr/lib64/python2.6/site-packages/xen/目錄下。具體在分析流程時,也是逐漸在源檔案中添加列印資訊,以達到一步步揭開該指令的執行流程。

1.1         問題描述

由于相關内容涉及面廣、牽扯面多,本文可能無法面面俱到,是以先給出一個中心點,即當時遇到的故障,基于這個問題,在解決此問題的基礎上,以求相對全面的分析xen添加網卡裝置的流程。

【故障】該故障會發生在遷移、虛拟機内部重新開機,外部reboot中。假設目前虛拟機有五塊網卡,ID 分别是 0,1,2,3,4,如果在遷移前,手動将 ID 為2 (或者0-3)的網卡删除,遷移後再添加網卡就會發生報告“Device xx is already connected”的錯誤。此後如果繼續添加網卡,又會添加成功。

1.2         指令流程

1.2.1          總體入口

指令流程都是通過逐漸在python腳本中添加列印得出的。首先看如下代碼:

xend/server/XMLRPCServer.py 檔案中 XMLRPCServer類中代碼:

try:

            while self.running:

                self.server.handle_request()

        finally:

            self.shutdown()

所有 xm 指令的入口都是 while 語句處,至于之前的一些初始化流程,那些過程屬于xend的初始化流程,對于xend整體的消息流程架構,本文暫時不深入追究。針對xm network-attach 指令而言,最終執行指令的入口處位于:

xend/XendDomainInfo.py 檔案XendDomainInfo類device_create函數中。

給出調用樹關系如下:

XendDomainInfo.device_create (self, dev_config)

self._createDevice(dev_type, dev_config_dict) 

self._waitForDevice(dev_type, devid)

下面分别介紹上面幾個主要函數。

1.2.2          建立裝置

建立裝置調用的函數如下:

    def _createDevice(self, deviceClass, devConfig):

        return self.getDeviceController(deviceClass).createDevice(devConfig)

檢視xend/XendDomainInfo.py檔案,無法簡單得出self.getDeviceController(deviceClass)的結構。通過添加列印得出,self.getDeviceController 傳回的類是xen.xend.server.netif.NetifController。在 tools/python/xen/xend/server/netif.py檔案中,可以看到類class NetifController(DevController)的源代碼。

顯然,self.getDeviceController(deviceClass).createDevice(devConfig)即調用

NetifController. createDevice(devConfig)。由于NetifController類自己沒有createDevice方法,根據面向對象的原則,其調用父類DevController的createDevice方法。

1.2.2.1         擷取前後端資訊

接着在DevController的createDevice方法中,看到如下代碼:

def createDevice(self, config):

        """Trigger the creation of a device with the given configuration.

        @return The ID for the newly created device.

        """

        (devid, back, front) = self.getDeviceDetails(config)

self.getDeviceDetails擷取前後端網卡的相關配置資訊,這裡需要暫時回到NetifController類中,因為DevController中的getDeviceDetails隻是一個空接口,具體函數在子類NetifController中。

def getDeviceDetails(self, config):

        """@see DevController.getDeviceDetails"""

        script  = config.get('script', xoptions.get_vif_script())

      ……此處省略

        if not mac:

            raise VmError("MAC address not specified or generated.")

        devid = self.allocateDeviceID() --- 從xenstore擷取網卡ID

        saveddevid = config.get('devid') – 從系統中擷取網卡ID

        if saveddevid is not None:

            log.info("change devid %d -> int(%s)", devid, str(saveddevid))

            devid=int(saveddevid)

……此處省略

        return (devid, back, front)

對于前文所說的故障而言,這個地方是一個關鍵點。該函數主要在擷取新增網卡裝置的配置資訊。紅色部分的代碼尤其關鍵,devid從 xenstore中擷取一個網卡ID, saveddevid從系統配置中擷取網卡ID,這個地方可能有點不太清楚,詳細說明如下:

如果網卡是新增的,那麼saveddevid 是 None;但如果是執行虛拟機内部重新開機、遷移等過程,可想而知,重新開機遷移等動作之前的網卡,在重新開機遷移後,網卡的配置資訊是不會丢失的,這說明系統中會儲存網卡的配置資訊,諸如mac、id等資訊,這些資訊在重新開機遷移等動作之後不會丢失。上述變量saveddevid就是從系統中讀取這類網卡的配置資訊。如果saveddevid 非空,則不使用xenstore自動配置設定的id,而使用原有的配置ID;相反,如果saveddevid為空,則使用xenstore自動配置設定的id。到這裡,邏輯上是沒有任何問題的。

順便說一下上述函數的傳回值,根據列印資訊得出:

devid 2 {'mac': '00:16:3e:6d:06:72', 'handle': '2', 'uuid': 'bec8d569-5594-7265-fcc8-9914283d95de', 'script': '/etc/xen/scripts/vif-bridge'} {'mac': '00:16:3e:6d:06:72', 'handle': '2'} 分别對應(devid, back, front)。

下面詳細分析下xenstore自動擷取網卡配置ID的函數。

1.2.2.1.1        Xenstore自動配置設定網卡id的機制

主要的函數就是上述代碼中的allocateDeviceID()。此時再回到在DevController的allocateDeviceID()方法,因為NetifController類沒有該方法,其調用父類DevController中的方法。

def allocateDeviceID(self):

        path = self.frontendMiscPath()

        return complete(path, self._allocateDeviceID)

complete 來自xen.xend.xenstore.xstransact 類,代碼如下:

def complete(path, f):

    while True:

        t = xstransact(path)

        try:

            result = f(t)

            if t.commit():

                return result

        except:

            t.abort()

            raise

def _allocateDeviceID(self, t):

        result = t.read("nextDeviceID")

        if result:

            result = int(result)

        else:

            result = 0

        t.write("nextDeviceID", str(result + 1))

        return result

結合上面三段代碼看,最後配置設定的函數實際是在 _allocateDeviceID 函數中。該函數調用t.read從xenstore中讀取 nextDeviceID的目前值,該值就是xenstore 自動配置設定的網卡裝置ID。同時,通過t.write将加一後的nextDeviceID值回寫到xenstore中。到這裡我們基本清楚xenstore網卡ID的配置設定機制,但對于xenstore的内部配置設定尚有不明之處,對此,我們繼續深入,分析下xenstore裡面的配置設定細節及相關通信機制。

1.2.2.1.2        Xenstore 與ID配置設定相關的内部通訊

我們從xen.xend.xenstore.xstransact類開始,關注下t.read與t.write的大緻機制。由于xenstore本身的複雜性,本文不會對該内容面面俱到。詳細請參考《xen虛拟化技術》一書中的第8.5節。

在xenstore.py 檔案中檢視 xstransact的調用處:

def complete(path, f):

    while True:

        t = xstransact(path) --- 調用 transaction_start

        try:

            result = f(t)  – 調用主體功能,此處是_allocateDeviceID

            if t.commit(): -- 調用 transaction_end

                return result

        except:

            t.abort()

            raise

在每次調用complete時,都會調用xstransact,該類的構造函數如下:

def __init__(self, path = ""):

        self.in_transaction = False # Set this temporarily -- if this

                                    # constructor fails, then we need to

                                    # protect __del__.

        assert path is not None

        self.path = path.rstrip("/")

        self.transaction = xshandle().transaction_start()

        self.in_transaction = True

上述代碼中的xshandle()來自xen.xend.xenstore.xsutil,xshandle()函數定義如下:

def xshandle():

    global xs_handle, xs_lock

    if not xs_handle:

        xs_lock.acquire()

        if not xs_handle:

            xs_handle = xen.lowlevel.xs.xs()

        xs_lock.release()

    return xs_handle

根據xshandle的代碼結構看出,其中的xs_handle是一個全局變量,非常類似一個“單例模式”,并不是每次都申請都初始化,相反,它初始化一次,卻會一直存在。進一步到xen.lowlevel.xs.xs()代碼中,此時已經到了python代碼與C代碼的結合之處,可以看出,這裡已經是通過python來調用C的功能,完成xenstore的相關功能。

xenstore本質上是一個檔案系統,所有對xenstore的操作與對linux下的檔案操作類似,包括讀、寫、建立目錄、設定檔案權限等,對應的消息類型如下:(來自xen.lowlevel.xs.xs):

static PyMethodDef xshandle_methods[] = {

    XSPY_METH(read,              METH_VARARGS),

    XSPY_METH(write,             METH_VARARGS),

    XSPY_METH(ls,                METH_VARARGS),

    XSPY_METH(mkdir,             METH_VARARGS),

    XSPY_METH(rm,                METH_VARARGS),

    XSPY_METH(get_permissions,   METH_VARARGS),

    XSPY_METH(set_permissions,   METH_VARARGS),

    XSPY_METH(watch,             METH_VARARGS),

    XSPY_METH(read_watch,        METH_NOARGS),

    XSPY_METH(unwatch,           METH_VARARGS),

    XSPY_METH(transaction_start, METH_NOARGS),

    XSPY_METH(transaction_end,   METH_VARARGS | METH_KEYWORDS),

    XSPY_METH(introduce_domain,  METH_VARARGS),

    XSPY_METH(set_target,        METH_VARARGS),

    XSPY_METH(resume_domain,     METH_VARARGS),

    XSPY_METH(release_domain,    METH_VARARGS),

    XSPY_METH(close,             METH_NOARGS),

    XSPY_METH(get_domain_path,   METH_VARARGS),

    { NULL },

};

xenstore的消息通訊機制依舊是基于環,詳細原理見《xen虛拟化技術》。

對于 t.read,t.write,主要功能代碼如下:

def _read(self, key):

        path = self.prependPath(key)

        return xshandle().read(self.transaction, path)

    def _write(self, key, data):

        path = self.prependPath(key)

        xshandle().write(self.transaction, path, data)

通過xshandle()的read和write方法,最終調用的還是xen.lowlevel.xs.xs檔案中對應的讀寫方法。通過添加列印,我們得出path的資訊:

path /local/domain/55/device-misc/vif/nextDeviceID

這與前文所述,xenstore是一個檔案系統吻合起來,path就是讀寫的路徑,通過執行xshandle().read 或者xshandle().write 方法對檔案中的路徑進行讀寫,限于篇幅,本文不再繼續羅列,讀者可以繼續往下深入分析,建議閱讀《xen虛拟化技術》相關章節。

實際上到這裡,還沒有真正的建立裝置,是以xenstore隻是一個中間的互動及存儲通信通道,它本身隻起到溝通和管理的作用。

這裡給出一個結論:此時通過向xenstore中擷取nextDeviceID值,或者寫入nextDeviceID的值,不會對實際裝置造成幹擾,因為現在還處于配置設定裝置資訊的前期,根本還不存在實際裝置。nextDeviceID的值僅僅決定下次新增網卡的ID值,該值對于網卡裝置而言,在同一台虛拟機上必須是唯一的。

1.2.2.2         與前後端互動

回到DevController類的createDevice函數中,接下來還是通過xenstore進行互動,向PV驅動的前後端發送消息。

首先擷取前後端的消息路徑:

(backpath, frontpath) = self.addStoreEntries(config, devid, back,

                                                     front)

通過列印得出:

backpath /local/domain/0/backend/vif/55/2

frontpath /local/domain/55/device/vif/2

在通過xenstore寫入前端之前,這裡先進行了一次檢查:

t = xstransact()

            try:

                if devid in self.deviceIDs(t):

                    if 'dev' in back:

                        dev_str = '%s (%d, %s)' % (back['dev'], devid,

                                                   self.deviceClass)

                    else:

                        dev_str = '%s (%s)' % (devid, self.deviceClass)

                    raise VmError("Device %s is already connected." % dev_str)

此時就與故障中的資訊對應起來,故障發生時列印出的錯誤,就是在這裡抛出的。

在新添加網卡時,self.deviceIDs(t)從xenstore中擷取了原有網卡的ID值,如果發現目前配置設定的網卡ID已經存在于xenstore中,則抛出錯誤。還是假設故障中的場景。綜合上面的分析,給出故障的原因:

xenstore 裡面有變量/local/domain/domid/device-misc/vif/nextDeviceID,如果網卡原來沒有ID,該值決定下次配置設定網卡的ID。如果網卡原來已經有ID,則使用網卡原來的ID。當每增加一塊網卡,nextDeviceID均會加1。遷移,内部重新開機等動作,虛拟機的網卡ID是不會丢失的,即都會沿用原來的網卡ID。

目前虛拟機有五塊網卡,ID 分别是 0,1,2,3,4 此時nextDeviceID = 5,即下次如果新添加網卡,網卡的ID将是5,同時會将nextDeviceID加1。

當虛拟機發生遷移、重新開機等動作後,nextDeviceID會被置0,網卡裝置會重新注冊到虛拟機,但是會沿用網卡原來的ID。

如果在遷移前,手動将 ID 為2 (或者0-3)的網卡删除,遷移後再添加網卡就會發生故障。假設遷移前删除ID為2 的網卡,此時剩餘網卡ID是 0,1,3,4。遷移後,nextDeviceID=0,此時0,1,3,4的網卡重新注冊到虛拟機,0,1,3,4網卡依次注冊後,nextDeviceID的值依次是1,2,3,4,即nextDeviceID總是從0開始,每次加1的增長。這個過程不會有問題,問題會發生在下一次添加網卡的過程。這個過程需要明白,0,1,3,4網卡重新注冊後,他們的ID是0,1,3,4,而不是0,1,2,3,因為它們原來是有ID的,是以不使用nextDeviceID的目前值。

當使用者下次添加新網卡時,由于新網卡顯然是不存在ID的,此時nextDeviceID決定網卡的ID,因為目前nextDeviceID=4,是以配置設定給新網卡的ID是4,此後更新nextDeviceID=5。此時問題來了,因為ID為4的網卡已經在系統中,故發生ID沖突,導緻網卡添加失敗。這就是遷移後第一次添加網卡失敗的原因。

使用者第二次添加網卡時,上次已經更新nextDeviceID=5,此時配置設定給新網卡的ID是5,因為目前nextDeviceID=5,此後更新nextDeviceID=6。由于系統中不存在ID為5的網卡,是以添加成功。這就是第二次添加網卡成功的原因。

對于上述結論,有幾點需要說明:

第一:虛拟機内部關閉或者遷移等動作後,nextDeviceID的值會清0,這是通過實驗和加列印得出的結論,具體代碼點沒有深入追究。

第二:虛拟機内部關閉或者遷移等動作後,網卡會依次重新向xenstore中注冊,原有網卡的配置資訊不會丢失。

第三: 一旦nextDeviceID被配置設定出去後,如果裝置最終沒有建立成功,代碼中不會對nextDeviceID的值進行復原處理,目前未發現該機制有問題。

xenstore得知前後端路徑後,加上之前擷取的網卡的配置資訊,将相應的資訊寫入到前後端中。具體代碼不再列出。

1.2.3          等待裝置

可以推測,xenstore通過向前後端發送消息後,前端和後端會根據驅動的相關功能,完成具體裝置的建立,這部分功能展現在PV驅動裡面,xen中所做的就是發送消息給前後端,以及等待他們的是否成功的響應。有關PV驅動前後端實際虛拟裝置的建立,将在單獨的文檔中進行說明。

此時回到xend/XendDomainInfo.py 檔案XendDomainInfo類device_create函數的_waitForDevice調用中。由于有了前面的介紹,我們不再走彎路,waitForDevice直接最終位于DevController 類中。

waitForDevice調用waitForBackend函數,waitForBackend函數中執行回調函數hotplugStatusCallback,并設定逾時時間,在逾時規定的時間内,觀察前後端裝置是否建立并連接配接成功,并将最後結果傳回,最後根據傳回結果做出相應的處理。

【故障解決方案】:本文就不給出具體解決方案。了解該錯誤的原理後,解決的辦法有許多。一個思路是:每次擷取自動分别的ID後,可以與舊網卡的ID進行比較,如果前者要小,則繼續擷取,當然這樣也會有其他的缺點,不再進一步描述。

後話:

通過寫此文檔,我發現對于添加網卡裝置而言,xen中所做的事情其實不多,但是并不容易描述,因為這小塊功能起點于一個比較龐大的架構之上,牽扯面廣。後面真正需要花功夫啃的還是xenstore的整體通信機制,以及PV驅動前後端的虛拟裝置建立過程。

繼續閱讀