anaconda

anaconda

anaconda,作為槍枝的名字,由柯爾特公司生產,該槍重量很大,在手把處加入了鉛塊,持續射擊容易炸膛,該槍運用於美國警備部門使用。在網路遊戲反恐精英online中以左輪軍魂的身份出現。此外有同名電影《anaconda》,在一些其他領域anaconda也有出現。

引言

anaconda 是 RedHat 的安裝程式。

Linux 的安裝過程可以分為兩個階段:

第一階段:載入核心,創建供後續安裝過程使用的系統環境

第二階段:載入系統安裝程式,執行具體的安裝過程。

對於第一階段,有不少資料作了比較詳細的介紹,而對於第二階段,也就是具體的安裝過程,卻鮮有資料介紹,這裡針對 redhat9.0 的安裝程式 anaconda 作分析。

anaconda 是用 python 語言寫的,所以最好具備一些 python 語言的基礎和面向對象的基本知識,如果有過圖形界面程式設計的經驗,則無論是對圖形安裝模式還是字元安裝模式安裝過程的理解都會有很大幫助。

概述

系統啟動,載入啟動映像,在記憶體中建立了 linux 系統環境後,解析安裝程式映像檔案,將安裝程式載入記憶體,執行主執行程式 anaconda ,該程式是具體安裝程式的一個入口,負責啟動具體的安轉過程,完成 linux 系統的安裝。

首先看一下安裝映像檔案。進入 redhat9.0 第一張系統盤的 redhat/base 目錄,其中 stage2.img 就是當安裝介質為 CD-ROM 時的安裝映像檔案。採用如下命令解析該映像檔案(注意:命令中的掛載點 /mnt/stage2 要根據實際情況修改):

mount -o loop /mnt/cdrom/Redhat/base/stage2.img /mnt/stage2

解析後可以看到,安裝程式的主執行體 anaconda 在 /mnt/stag2/usr/bin 目錄下,其它安裝腳本模組均在 /mnt/stage2/usr/lib/anaconda 目錄下,今後的行文中,除非是 anaconda 主執行體,其餘均指 /mnt/stage2/usr/lib/anaconda 目錄下的模組,並將該目錄簡稱為 anaconda 主目錄。

anaconda 主目錄結構

installclasses子目錄中的各個模組定義了在安裝過程中用戶可選擇的安裝類型。redhat9.0 下包含了四個檔案 workstation.py,server.py,custom.py 和 personal_desktop.py 。

workstation.py 描述了工作站安裝類型

server.py 描述了伺服器安裝類型

custom.py 描述了用戶自定義安裝類型

personal_desktop.py 描述了個人桌面安裝類型。

每個安裝類型描述檔案根據相應安裝類型的特點,分別對安裝步驟、分區策略以及安裝包的取捨給出了不同的方案。

Iw 子目錄下包含所有安裝圖形界面類所在的模組,每個圖形界面對應一個,負責相應安裝步驟圖形界面的具體外觀顯示及與用戶的互動,(可能)調用 anaconda 主目錄下的相關安裝行為模組完成具體的安裝操作。

textw 子目錄iw 子目錄含義是一致的,只是包含的是字元安裝模式的前端字元用戶界面類所在的模組,每個字元用戶界面對應一個類,負責與用戶的互動,字元界面的採用了 python 的 snack 庫

如果說用戶界面類是處理安裝程式外觀的話,則 anaconda 主目錄下的各 python 模組則執行每個安裝界面背後具體的安裝行為,包括那些無用戶界面安裝步驟的安裝操作。

Python 的許多內置模組在目錄 /mnt/stage2/usr/lib/pythonXX 下,其中 XX 指版本號。另外可參考 redhat 的 anaconda 源碼包,所有的相關工具都在源碼包里,在理解了安裝過程的原理後,可修改源碼,重新編譯,然後搭建測試環境進行測試。

總體分析

圖形安裝界面採用 python + Gtk 開發,各個模組(類)之間的邏輯關係可用下圖表示:

anaconda圖形安裝模式下模組(類)邏輯關係圖

安裝程式邏輯上可劃分為三個層次,先看最主要的一層:調度中心。可以說,Dispatcher類InstallControlWindow類控制了整個安裝程式的運轉,理解這兩個核心類的功能及其與用戶界面類和安裝行為類的邏輯關係,就基本上理解了整個安裝程式。

Dispatcher類在anaconda主目錄下的dispatch.py模組中,負責整個安裝流程的控制,在安裝過程中,某些安裝步驟有的需要前置安裝操作,有的又需要後置安裝操作,而某些安裝操作則是沒有用戶界面的,統稱這些安裝操作為無用戶界面的安裝操作,那么,這些沒有用戶界面的安裝操作由誰來調度運行呢?答案就是Dispatcher。

InstallControlWindow 類在 anaconda 主目錄下的 gui.py模組中,控制安裝過程中前端圖形界面的顯示,總體調度各個安裝圖形界面類,InstallControlWindow 建立圖形界面的主窗體,每個具體的圖形安裝界面可視為其子窗體。InstallControlWindow 調用 Dispatcher 控制整個安裝流程。

安裝過程中,每個具體的圖形安裝界面均對應一個具體的類,由其對應的具體的類完成具體的安裝任務,將這些圖形界面類歸為前端顯示層,位於iw目錄下的模組中。

anaconda將某些用戶界面類中的具體安裝操作分離出來,放到了另外一些模組中,這些模組放在了anaconda的主目錄下。由Dispatcher直接調用的沒有用戶界面的安裝步驟對應的函式也放在了anaconda的主目錄下,將這些模組歸為安裝行為層。

圖中虛線表達的含義是"可能調用"。如在安裝過程中,負責前端顯示的package_gui.py模組可能要調用負責安裝行為的packages.py模組或其它負責安裝行為模組中的類或函式。

主要控制類分析

Dispatcher

該類在anaconda主目錄下的dispatch.py模組中。無論是圖形模式安裝還是字元模式安裝,都由該類來控制安裝流程,所以,分析該類時將圖形用戶界面和字元用戶界面統稱為用戶界面,涉及到字元安裝模式的敘述,讀者暫時可略過,讀到字元安裝模式分析時,再回頭來看。dispatch.py模組中有一個序列(sequence)數據結構:installSteps。installSteps中記錄了有序排列的整個安裝過程中所有可能的安裝步驟,在生成具體的Dispatcher實例時,會根據安裝類型制定對此進行相應裁減。

installSteps中的條目(item)有如下兩種格式,第一種格式:( name, tuple)

這種格式表示有用戶界面的安裝步驟。其中,name代表安裝步驟的名稱,tuple(元組,python的一種內置數據類型)存放創建相應安裝步驟的用戶界面的參數。

第二種格式:( name, Function, tuple)

這種格式表示沒有用戶界面的安裝步驟,其中,name代表安裝步驟的名稱,Function指安裝操作的具體執行函式,tuple存放的是傳遞給Function的參數。該安裝步驟直接由Dispatcher調度進行。

下面結合具體的程式具體地解釋一下上面的敘述,加深對安裝流程的理解。

首先看下面從序列installSteps中截取的一個程式片斷:

installSteps = [
……
("partitionmethod", ("id.partitions", "id.instClass")),
("partitionobjinit", partitionObjectsInitialize, ("id.diskset","id.partitions","dir", "intf")),
("partitionmethodsetup", partitionMethodSetup, ("id.partitions","dispatch")),
("autopartition", ("id.diskset", "id.partitions", "intf", "dispatch")),
("autopartitionexecute", doAutoPartition, ("dir", "id.diskset","id.partitions", "intf",
"id.instClass", "dispatch")),
("fdisk", ("id.diskset", "id.partitions", "intf")),
……
]

假設當前安裝步驟為partitionmethod,根據其條目的數據格式可以看出該安裝步驟具有用戶界面,根據安裝步驟名稱,如果熟悉linux安裝過程,應該可以猜出這個安裝步驟是分區方式選擇,對應該安裝步驟的用戶界面的類是PartitionMethodWindow,該類屬於前端顯示層,在iw目錄下partitionmethod_gui.py模組中。看一下該類的部分代碼:

class PartitionMethodWindow(InstallWindow):
def getNext(self):
if self.useAuto.get_active():
self.partitions.useAutopartitioning = 1
else:
self.partitions.useAutopartitioning = 0
……

可以想像當用戶在用戶界面上點擊"下一步"按鈕時getNext函式被調用,分析該函式,可以看出,當用戶選擇自動分區後,該函式設定partitions.useAutopartitioning = 1,如果選擇手動分區,則設定partitions.useAutopartitioning = 0。

本步驟完成,安裝過程進行下一步,Dispatcher首先在installSteps中取下一個條目"partitionobjinit",該條目中含有function,說明此安裝步驟沒有用戶界面,Dispatcher直接調用函式(Function)partitionObjectsInitialize,從字面上我們就可以看出,該函式是對分區操作進行相關初始化方面的操作,執行完該安裝步驟後,Dispatcher繼續調用下一個安裝操作"partitionmethodsetup",該安裝步驟也沒有用戶界面,Dispatcher直接調用函式partitionMethodSetup,函式partitionMethodSetup和partitionObjectsInitialize都屬於安裝行為層,在anaconda主目錄下的partitioning.py模組中。看一下函式partitionMethodSetup的部分代碼:

def partitionMethodSetup(partitions, dispatch):
dispatch.skipStep("autopartition", skip = not partitions.useAutopartitioning)
dispatch.skipStep("autopartitionexecute", skip = not partitions.useAutopartitioning)
……

可以看出,如果用戶選擇手動分區,即partitions.useAutopartitioning = 0,則not partitions.useAutopartitioning) = 1,即skip = 1,那么安裝流程控制實例Dispatcher就會略過(skipsteip)安裝步驟"autopartition"和"autopartitionexecute"繼續下一個安裝步驟,下一個安裝步驟有用戶界面,對於圖形安裝模式,Dispatcher將控制權交給InstallControlWindow,對於字元安裝模式,Dispatcher將控制權交給InstallInterface。

再分析另一個從installSteps中截取的片斷:

installSteps = [
……
("bootloaderadvanced", ("dispatch", "id.bootloader", "id.fsset", "id.diskset")),
("networkdevicecheck", networkDeviceCheck, ("id.network", "dispatch")),
("network", ("id.network", "dir", "intf")),
……
]

假設當前安裝步驟為"bootloaderadvanced",其後的另一個具有用戶界面的安裝步驟為網路配置"network",而進行網路配置的前置條件就是安裝的主機要具有網路設備,因此,系統安裝程式在"network"之前插入了步驟"networkdevicecheck"。假設安裝過程從當前安裝步驟"bootloaderadvanced"進行到下一個安裝步驟"networkdevicecheck",該安裝步驟沒有用戶界面,Dispatcher直接調用函式networkDeviceCheck,看一下該函式的代碼(network.py):

def networkDeviceCheck(network, dispatch):
devs = network.available()
if not devs:
dispatch.skipStep("network")

可以看到,該函式首先檢測是否存在網路設備,如果沒有網路設備,Dispatcher則忽略安裝步驟"network"。這裡,可以說函式networkDeviceCheck是有用戶界面的安裝步驟"network"的前置操作。

通過上面的分析,不難理解為什麼Dispatcher被稱為"主要安裝流程控制類"了。其它的安裝步驟控制基本相同。

下面看一下Dispatcher的主要接口。

gotoNext & gotoPrev:這兩個接口分別從當前安裝步驟前進(後退)到下一個(上一個)具有用戶界面的安裝步驟,在圖形界面安裝模式下,由installcontrolwindow調用,在字元模式下,由InstallInterface調用。這兩個函式只是簡單的設定安裝方向,然後調用movestep函式,其核心操作是movestep。這裡來重點分析movestep函式:

def moveStep(self):
……
if self.step == None:
self.step = self.firstStep
else:
self.step = self.step + self.dir
while ((self.step >= self.firstStep and self.step < len(installSteps))
and (self.skipSteps.has_key(installSteps[self.step][0])
or (type(installSteps[self.step][1]) == FunctionType))):
info = installSteps[self.step]
if ((type(info[1]) == FunctionType) and (not self.skipSteps.has_key(info[0]))):
(func, args) = info[1:]
rc = apply(func, self.bindArgs(args))
if rc == DISPATCH_BACK:
self.dir = -1
elif rc == DISPATCH_FORWARD:
self.dir = 1
self.step = self.step + self.dir
if self.step == len(installSteps):
return None

重點看一下程式while循環體,首先看一下循環條件:當下一個安裝步驟是合法的,即在第一個安裝步驟和最後一個安裝步驟之間,並且(and)該步驟被裁減了或者該步驟是一個無用戶界面的安裝步驟,即installSteps的條目的第二個元素是一個function,則進入循環體。進入循環後,Dispatcher直接調用該函式執行安裝操作,其中bindArgs是Dispatcher類的一個函式,負責參數解析,這裡的apply是python的的一個內置方法,用來執行函式,apply接口的第一個參數是要運行的函式名稱,第二個參數是傳給該函式的參數。如果下一個安裝步驟依然無用戶界面,則繼續循環,直到下一個沒有被裁減的具有用戶界面的安裝步驟,對於圖形安裝模式,Dispatcher將控制權交給InstallControlWindow,對於字元安裝模式,Dispatcher將控制權交給InstallInterface。如果安裝過程完成則退出循環。

currentStep:Dispatcher類的另一個主要接口,取得當前的安裝步驟及其相關信息返回給調用者。在圖形安裝模式下,該函式主要由InstallControlWindow調度圖形用戶界面類時調用,在字元模式下,主要由InstallInterface調度字元用戶界面時調用,這兩個類通過該接口取得當前安裝步驟的用戶界面對應的類及創建該用戶界面類的實例所需的信息。

另外,Dispatcher類的主要接口還有skipStep(self, stepToSkip, skip = 1, permanent = 0)是裁減安裝步驟函式。setStepList(self, *steps)是安裝步驟設定函式,主要由安裝類型實例調用,每個安裝類型會根據自身的特點設定安裝步驟。這些接口的實現邏輯都比較簡單,這裡不一一給出分析了。

至此,Dispatcher的主體基本分析完了,看完下面InstallControlWindow以及後面字元安裝模式的InstallInterface類的分析,對Dispatcher將會有更好的理解。

InstallControlWindow

該類在anaconda主目錄下的gui.py模組中。首先先看一下啟動圖形安裝界面的入口函式run:

def run (self, runres, configFileData):
self.configFileData = configFileData
self.setup_window(runres)
gtk.main()

該函式調用了setup_window接口,該接口調用gtk"繪製"圖形安裝界面的主窗體,然hou控制權交給了gtk。

nextClicked & prevClicked:這兩個接口分別執行從當前圖形安裝界面向前(向後)到下一個圖形安裝界面的操作,可以想像安裝過程中當用戶點擊"下一步"或"上一步"按鈕時,這兩個函式被調用。這兩個函式首先調用主流程控制Dispatcher實例向前(向後)前進到下一個圖形安裝界面,然後調用setScreen函式,從函式名稱的字面上看,setScreen是設定圖形界面的,那么接下來我們看一看該函式的具體功能,在分析其之前,要首先看一下gui.py模組中的一個字典數據結構:stepToClass。該字典中記錄了安裝過程中所有的具有圖形用戶界面的安裝步驟。

stepToClass = {
"language" : ("language_gui", "LanguageWindow"),
"keyboard" : ("keyboard_gui", "KeyboardWindow"),
……
}

每一個條目從左到右依次是安裝步驟名稱、圖形界面對應的類所在的模組,圖形界面類的名稱。如language為安裝步驟名稱,language_gui為該步驟對應的圖形界面類所在的模組language_gui.py,LanguageWindow為圖形界面對應的類名。理解了該數據結構後,開始分析setScreen函式:

def setScreen (self):
(step, args) = self.dispatch.currentStep()
if not stepToClass[step]:
if self.dir == 1:
return self.nextClicked()
else:
return self.prevClicked()
(file, className) = stepToClass[step]
newScreenClass = None
s = "from %s import %s; newScreenClass = %s" % (file, className, className)
while 1:
exec s
break
self.destroyCurrentWindow()
self.currentWindow = newScreenClass(ics)
new_screen = apply(self.currentWindow.getScreen, args)
self.installFrame.add(new_screen)
self.installFrame.show_all()
……

前面的nextClicked和prevClicked函式已經通過Dispatcher將要進行的安裝步驟標記為當前安裝步驟,所以該函式首先通過Dispatcher的currentStep從Dispatcher的數據結構installSteps中取得當前安裝步驟名稱及相關信息,接下來,做了一下判斷,如果Dispatcher的當前安裝步驟不在字典stepToClass中,則忽略該步驟,調用nextClicked或prevClicked繼續下一個圖形界面安裝步驟,直到下一個步驟在字典stepToClass中。驗證通過後,從字典stepToClass中取得當前圖形安裝界面對應的類及該類所在模組,然後導入該模組並創建圖形安裝界面的實例,銷毀前一個圖形安裝界面,並將新創建的圖形界面實例置為當前安裝界面,調用圖形安裝界面實例的getScreen函式生成該安裝步驟的圖形用戶界面,然後顯示。

至此,InstallControlWindow的主要邏輯已經分析完了,接下來涉及每個具體安裝界面及其安裝操作讀者可以到iw目錄下逐個深入分析。

主執行程式anaconda分析

前面比較詳細的分析了圖形模式和字元模式的安裝過程,對於圖形安裝過程,圖形環境運行是建立在X Server基礎上的,因此在進行圖形安裝時,必須運行X。因此,簡單的說,對於圖形模式,anaconda的主執行體就作了兩件事:1、運行X伺服器(如果X沒有運行)。2、啟動圖形模式安裝過程。而對於字元模式,anaconda的主執行體就作了一件事,啟動字元模式安裝過程。

啟動X server

如果是圖形模式安裝,則anaconda首先要啟動X server。anaconda首先檢測圖形正常顯示所需的硬體資源。Python提供了許多內置模組實現相關硬體的操作。這些模組在目錄pythonXX/rhpl下。

Videocard.py、Monitor.py、Mouse.py、keyboard.py分別是顯示卡監視器滑鼠鍵盤的相關操作和配置數據。xserver.py - 圖形模式下X server啟動初始化方面的工作,包括相關硬體的探測和X server的啟動,xserver為videcard、monitor和mouse的探測提供了統一個接口,實際調用的是相應硬體模組的接口。

下邊的程式片段探測顯示卡、監視器和滑鼠、鍵盤,並且利用xhwstate記錄該配置狀態,供後續安裝過程使用。

import rhpl.xserver as xserver
(videohw, monitorhw, mousehw) = xserver.probeHW(skipDDCProbe=skipddcprobe,
skipMouseProbe = skipmouseprobe)
kbd = keyboard.Keyboard()
if keymap:
kbd.set(keymap)
xcfg = xhwstate.XF86HardwareState(defcard=videohw, defmon=monitorhw)

硬體檢測完成後,啟動X server的條件已就緒,如果X server沒有啟動,並且安裝模式為圖形模式,則啟動X server:

xsetup_failed = xserver.startXServer(videohw, monitorhw, mousehw, kbd,runres,
xStartedCB=doStartupX11Actions,
xQuitCB=doShutdownX11Actions,
logfile=xlogfile)

該調用除了傳遞相關硬體的配置信息外,還傳遞了兩個函式。

doStartupX11Actions函式在X server啟動時調用,該函式主要是啟動視窗管理器,設定相關資源。

doShutdownX11Actions函式在X server退出時調用,關閉視窗管理器。

啟動安裝過程

前面分析安裝過程原理時指出:安裝的流程由Dispatcher控制,對於圖形模式,圖形模式的前端顯示及與用戶的互動由InstallControlWindow調度,而字元模式的前端顯示層由InstallInterface調度。因此,啟動安裝過程,實際就是創建主要控制類的實例,調用實例的接口,啟動安裝過程,然後再由這幾個主要的控制類的實例創建具體安裝界面,創建安裝行為類的實例,調用具體的函式完成具體的安裝過程。

創建實例
1. 創建dispatch實例

dispatch = dispatch.Dispatcher(intf, id, methodobj, rootPath)

2. 創建InstallInterface實例

對於字元模式,前端字元用戶界面由InstallInterface調度。而對於圖形模式,我們在前面分析時,沒有提到InstallInterface類,其實在圖形模式下,也有這么一個同名類,在模組gui.py中,但在圖形安裝模式下,這個類的作用很小,除了封裝了創建常用對話框(包括messageWindow,progressWindow,exceptionWindow等)的接口外,一個主要的作用就是為創建installcontrolwindow實例提供了一個接口,為什麼不在anaconda主執行程式中直接創建installcontrolwindow實例呢?個人認為可能是為了保證代碼的一致性。

if (display_mode == 'g'):
from gui import InstallInterface
if (display_mode == 't'):
from text import InstallInterface
if display_mode == "t":
intf = InstallInterface ()
else:
intf = InstallInterface ()

3. 創建installcontrolwindow實例

installcontrolwindow實例在gui.py模組中的InstallInterface類的接口run中創建,該接口創建了InstallControlWindow實例後運行InstallControlWindow實例的run接口啟動圖形安裝界面:

def run(self, id, dispatch, configFileData):
……
self.icw = InstallControlWindow (self, self.dispatch, lang)
self.icw.run (self.runres, configFileData)

啟動安裝過程

安裝控制類的實例創建完成後,調用其相應接口啟動安裝過程。對於字元安裝模式,則直接調用InstallInterface實例的run接口。而對於圖形安裝模式,則是由InstallInterface實例的run接口間接的調用installcontrolwindow實例的run接口。

intf.run(id, dispatch, configFileData)

另外,在啟動安裝過程前,anacoanda的主執行程式作了大量的繁瑣的準備工作,主要包括引用模組路徑設定、參數解析、記憶體檢測、安裝類型設定、創建安裝介質實例等等。

相關詞條

相關搜尋

熱門詞條

聯絡我們