本來想寫篇關於Anaconda的文章,但看到這裡寫的這麼詳細,轉,原文在這裡:Linux安裝程式Anaconda分析(續)
(1) disptach.py: 下面我們看一下Dispatcher類的主要介面。
1)gotoNext & gotoPrev:這兩個介面分別從當前安裝步驟前進(後退)到下一個(上一個)具有使用者介面的安裝步驟,在圖形介面安裝模式下,由InstallControlWindow類調用,在字元模式下,由InstallInterface類(在text.py和cmdline.py中)調用。這兩個函數只是簡單的設定安裝方向,然後調用moveStep函數,其核心操作是moveStep。
2)moveStep:我們來重點分析movestep函數,代碼如下:
def moveStep(self): if self.step == None: self.step = self.firstStep else: if self.step >= len(installSteps): return None log.info("leaving (%d) step %s" %(self._getDir(), installSteps[self.step][0])) self.step = self.step + self._getDir() if self.step >= len(installSteps): return None while self.step >= self.firstStep and self.step < len(installSteps) \ and (self.stepInSkipList(self.step) or self.stepIsDirect(self.step)): if self.stepIsDirect(self.step) and not self.stepInSkipList(self.step): (stepName, stepFunc) = installSteps[self.step] log.info("moving (%d) to step %s" %(self._getDir(), stepName)) log.debug("%s is a direct step" %(stepName,)) rc = stepFunc(self.anaconda) if rc in [DISPATCH_BACK, DISPATCH_FORWARD]: self._setDir(rc) log.info("leaving (%d) step %s" %(self._getDir(), stepName)) # if anything else, leave self.dir alone self.step = self.step + self._getDir() if self.step == len(installSteps):# 安裝過程完成,退出迴圈 return None if (self.step < 0): # pick the first step not in the skip list self.step = 0 while self.skipSteps.has_key(installSteps[self.step][0]): self.step = self.step + 1# 步數加一,向前 elif self.step >= len(installSteps): self.step = len(installSteps) - 1 while self.skipSteps.has_key(installSteps[self.step][0]): self.step = self.step - 1 log.info("moving (%d) to step %s" %(self._getDir(), installSteps[self.step][0]))
我們重點看一下程式while迴圈體,首先看一下迴圈條件:當下一個安裝步驟是合法的,即在第一個安裝步驟和最後一個安裝步驟之間,並且(and)該步驟被跳過或者該步驟是一個無使用者介面的安裝步驟,即installSteps的條目的第二個元素是一個function,則進入迴圈體。進入迴圈後,Dispatcher直接調用該函數stepFunc執行安裝操作。如果下一個安裝步驟依然無使用者介面,則步數加一向前,繼續迴圈,直到下一個沒有被跳過的具有使用者介面的安裝步驟,對於圖形安裝模式,Dispatcher將控制權交給guid.py中的InstallControlWindow,對於字元安裝模式,Dispatcher將控制權交給InstallInterface。如果安裝過程完成則退出迴圈。
3)currentStep:Dispatcher類的另一個主要介面,取得當前的安裝步驟及其相關資訊返回給調用者。在圖形安裝模式下,該函數主要在InstallControlWindow調度圖形化使用者介面類時調用,在字元模式下,主要在InstallInterface調度字元使用者介面時調用,這兩個類通過該介面取得當前安裝步驟的使用者介面對應類及建立該使用者介面類的執行個體所需的資訊。
def currentStep(self): if self.step == None: self.gotoNext() elif self.step >= len(installSteps): return (None, None) stepInfo = installSteps[self.step] step = stepInfo[0] return (step, self.anaconda)
另外,Dispatcher類的主要介面還有skipStep(self, stepToSkip, skip = 1, permanent = 0)是跳過安裝步驟的函數。setStepList(self, *steps)是安裝步驟設定函數,主要由安裝類型執行個體調用,每個安裝類型會根據自身的特點設定安裝步驟。這些介面的實現邏輯都比較簡單,這裡不一一給出分析了。
(2)gui.py: 核心是字元安裝模式的InstallInterface類和圖形安裝模式InstallControlWindow類的實現。看InstallControlWindow中的介面。
1)資料結構stepTopClass: 該字典中記錄了安裝過程中所有的具有圖形化使用者介面的安裝步驟。
stepToClass = { "language" : ("language_gui", "LanguageWindow"), "keyboard" : ("kbd_gui", "KeyboardWindow"), "filtertype" : ("filter_type", "FilterTypeWindow"), "filter" : ("filter_gui", "FilterWindow"), "zfcpconfig" : ("zfcp_gui", "ZFCPWindow"), "partition" : ("partition_gui", "PartitionWindow"), "parttype" : ("autopart_type", "PartitionTypeWindow"), "cleardiskssel": ("cleardisks_gui", "ClearDisksWindow"), "findinstall" : ("examine_gui", "UpgradeExamineWindow"), "addswap" : ("upgrade_swap_gui", "UpgradeSwapWindow"), "upgrademigratefs" : ("upgrade_migratefs_gui", "UpgradeMigrateFSWindow"), "bootloader": ("bootloader_main_gui", "MainBootloaderWindow"), "upgbootloader": ("upgrade_bootloader_gui", "UpgradeBootloaderWindow"), "network" : ("network_gui", "NetworkWindow"), "timezone" : ("timezone_gui", "TimezoneWindow"), "accounts" : ("account_gui", "AccountWindow"), "tasksel": ("task_gui", "TaskWindow"), "group-selection": ("package_gui", "GroupSelectionWindow"), "install" : ("progress_gui", "InstallProgressWindow"), "complete" : ("congrats_gui", "CongratulationWindow"),}
每一個條目從左至右依次是安裝步驟名稱、圖形介面類所在模組,圖形介面類的名稱。如language為安裝步驟名稱,language_gui為該步驟對應的圖形介面類所在模組language_gui.py,LanguageWindow為圖形介面對應的類名。
2)run: 啟動圖形安裝介面的入口函數。該函數調用了setup_window介面,該介面調用gtk"繪製"圖形安裝介面的主表單,然後控制權交給了gtk
def run (self): self.setup_theme() self.setup_window(False) gtk.main()
3)nextClicked & prevClicked:這兩個介面分別執行從當前圖形安裝介面向前(向後)到下一個圖形安裝介面的操作,我們可以想象安裝過程中當使用者點擊"下一步" 或"上一步"按鈕時,這兩個函數被調用。這兩個函數首先調用主流程式控制制Dispatcher執行個體向前(向後)前進到下一個圖形安裝介面,然後調用setScreen函數設定圖形介面。
def prevClicked (self, *args): try: self.currentWindow.getPrev () except StayOnScreen: return self.anaconda.dispatch.gotoPrev() self.setScreen () def nextClicked (self, *args): try: rc = self.currentWindow.getNext () except StayOnScreen: return self.anaconda.dispatch.gotoNext() self.setScreen ()4)setScreen: 用於設定圖形介面。代碼如下: def setScreen (self):# 取得當前安裝步驟資訊 (step, anaconda) = self.anaconda.dispatch.currentStep() if step is None: gtk.main_quit() return if not stepToClass[step]:# 不在其中,則直接跳到下一步 if self.anaconda.dispatch.dir == DISPATCH_FORWARD: return self.nextClicked() else: return self.prevClicked() (file, className) = stepToClass[step]# 獲得圖形介面類所在模組及其類名 newScreenClass = None while True: try: found = imp.find_module(file, iw.__path__) moduleName = 'pyanaconda.iw.%s' % file loaded = imp.load_module(moduleName, *found)# 載入該圖形介面模組 newScreenClass = loaded.__dict__[className] break except ImportError, e: stdout_log.error("loading interface component %s" % className) stdout_log.error(traceback.format_exc()) win = MessageWindow(_("Error!"), _("An error occurred when attempting " "to load an installer interface " "component.\n\nclassName = %s") % (className,), type="custom", custom_icon="warning", custom_buttons=[_("_Exit"), _("_Retry")]) if not win.getrc(): msg = _("The system will now reboot.") buttons = [_("_Reboot")] MessageWindow(_("Exiting"), msg, type="custom", custom_icon="warning", custom_buttons=buttons) sys.exit(0) ics = InstallControlState (self)# 設定是否是可以返回上一步 ics.setPrevEnabled(self.anaconda.dispatch.canGoBack()) self.destroyCurrentWindow()# 銷毀原來的圖形安裝介面 self.currentWindow = newScreenClass(ics)# 建立新的圖形安裝介面並設為當前介面 new_screen = self.currentWindow.getScreen(anaconda)# 產生安裝步驟的介面 # If the getScreen method returned None, that means the screen did not # want to be displayed for some reason and we should skip to the next # step. However, we do not want to remove the current step from the # list as later events may cause the screen to be displayed. if not new_screen: if self.anaconda.dispatch.dir == DISPATCH_FORWARD: self.anaconda.dispatch.gotoNext() else: self.anaconda.dispatch.gotoPrev() return self.setScreen() self.update (ics) self.installFrame.add(new_screen) self.installFrame.show_all() self.currentWindow.focus() self.handle = gobject.idle_add(self.handleRenderCallback) if self.reloadRcQueued: self.window.reset_rc_styles() self.reloadRcQueued = 0
前面的nextClicked和prevClicked函數已經通過Dispatcher將要進行的安裝步驟標記為當前安裝步驟,所以該函數首先通過Dispatcher的currentStep從Dispatcher的資料結構installSteps中取得當前安裝步驟名稱及相關資訊,接下來,做了一下判斷,如果Dispatcher的當前安裝步驟不在字典stepToClass中,則忽略該步驟,調用nextClicked或prevClicked繼續下一個圖形介面安裝步驟,直到下一個步驟在字典stepToClass中。驗證通過後,從字典stepToClass中取得當前圖形安裝介面對應的類及該類所在模組,然後匯入該模組並建立圖形安裝介面的執行個體,銷毀前一個圖形安裝介面,並將新建立的圖形介面執行個體置為當前安裝介面,調用圖形安裝介面執行個體的getScreen函數產生該安裝步驟的圖形化使用者介面,然後顯示。
至此,InstallControlWindow的主要邏輯已經分析完了,接下來涉及每個具體安裝介面及其安裝操作讀者可以到iw目錄下逐個深入分析。
(3)anaconda主程式: 圖形環境運行是建立在X Server基礎上的,對於圖形模式,anaconda需要先運行X伺服器,然後啟動圖形模式安裝過程。而對於字元模式,anaconda的主執行體就作了一件事,啟動字元模式安裝過程。
if __name__ == "__main__": setupPythonPath() # ...... # 解析啟動本指令碼時傳入的參數 (opts, args) = parseOptions() from pyanaconda.flags import flags if opts.images: flags.imageInstall = True # 設定log import logging from pyanaconda import anaconda_log anaconda_log.init() log = logging.getLogger("anaconda") stdoutLog = logging.getLogger("anaconda.stdout") # ...... from pyanaconda import Anaconda anaconda = Anaconda()# 建立主執行體執行個體 warnings.showwarning = AnacondaShowWarning iutil.setup_translations(gettext) # ...... # 檢測記憶體,現在只用在文字模式下 check_memory(anaconda, opts, 't') if opts.unsupportedMode: stdoutLog.error("Running anaconda in %s mode is no longer supported." % opts.unsupportedMode) sys.exit(0) # ...... # kickstart檔案解析 if opts.ksfile: kickstart.preScriptPass(anaconda, opts.ksfile) anaconda.ksdata = kickstart.parseKickstart(anaconda, opts.ksfile) opts.rescue = opts.rescue or anaconda.ksdata.rescue.rescue # ...... # 如果沒有X server,使用文字模式 if not flags.livecdInstall and not iutil.isS390() and not os.access("/usr/bin/Xorg", os.X_OK): stdoutLog.warning(_("Graphical installation is not available. " "Starting text mode.")) time.sleep(2) anaconda.displayMode = 't' # ...... # 啟動本地的X server if anaconda.displayMode == 'g' and not flags.preexisting_x11 and not flags.usevnc: try: # start X with its USR1 handler set to ignore. this will make it send # us SIGUSR1 if it succeeds. if it fails, catch SIGCHLD and bomb out. def sigchld_handler(num, frame): raise OSError(0, "SIGCHLD caught when trying to start the X server.") def sigusr1_handler(num, frame): log.debug("X server has signalled a successful start.") def preexec_fn(): signal.signal(signal.SIGUSR1, signal.SIG_IGN) old_sigusr1 = signal.signal(signal.SIGUSR1, sigusr1_handler) old_sigchld = signal.signal(signal.SIGCHLD, sigchld_handler) xout = open("/dev/tty5", "w")# 啟動X server proc = subprocess.Popen(["Xorg", "-br", "-logfile", "/tmp/X.log", ":1", "vt6", "-s", "1440", "-ac", "-nolisten", "tcp", "-dpi", "96", "-noreset"], close_fds=True, stdout=xout, stderr=xout, preexec_fn=preexec_fn) signal.pause() os.environ["DISPLAY"] = ":1" doStartupX11Actions() except (OSError, RuntimeError) as e: stdoutLog.warning(" X startup failed, falling back to text mode") anaconda.displayMode = 't' graphical_failed = 1 time.sleep(2) finally: signal.signal(signal.SIGUSR1, old_sigusr1) signal.signal(signal.SIGCHLD, old_sigchld) set_x_resolution(opts.runres) # ...... # 初始化UI介面 anaconda.initInterface() anaconda.instClass.configure(anaconda) # ...... # 啟動安裝過程 try: anaconda.intf.run(anaconda) except SystemExit, code: anaconda.intf.shutdown() if anaconda.ksdata and anaconda.ksdata.reboot.eject: for drive in anaconda.storage.devicetree.devices: if drive.type != "cdrom": continue log.info("attempting to eject %s" % drive.path) drive.eject() del anaconda.intf
主要工作包括引用模組路徑設定、參數解析、設定log、記憶體檢測、安裝類型設定,然後調用pyanaconda/__init__.py::Anaconda類建立主執行體執行個體anaconda,接著解析kickstart檔案,調用/usr/bin/Xorg(位於解開後的install.img中)程式啟動X server,調用Anaconda類的initInterface()初始化介面,調用intf(是InstallInterface類的執行個體)的run()啟動安裝過程。對於字元安裝模式,是直接調用InstallInterface執行個體的run介面。而對於圖形安裝模式,則是由InstallInterface執行個體的run介面間接的調用installcontrolwindow執行個體的run介面,從而啟動圖形介面。
(4)pyanaconda/__init__.py: 裡面有Anaconda類,負責具體的啟動安裝過程。前面說過,安裝的流程由Dispatcher控制,對於圖形模式,圖形模式的最上層顯示及與使用者的互動由InstallControlWindow調度,而字元模式的最上層顯示層由InstallInterface調度。因此,啟動安裝過程,實際就是建立主要控制類的執行個體,調用執行個體的介面,啟動安裝過程,然後再由這幾個主要的控制類的執行個體建立具體安裝介面,建立安裝行為類的執行個體,調用具體的函數完成具體的安裝過程。
class Anaconda(object): def __init__(self): import desktop, dispatch, firewall, security import system_config_keyboard.keyboard as keyboard from flags import flags # ......# 建立dispatch執行個體 self.dispatch = dispatch.Dispatcher(self) # ...... # ...... intf = property(_getInterface, _setInterface, _delInterface) # ...... def initInterface(self): if self._intf: raise RuntimeError, "Second attempt to initialize the InstallInterface" # 設定圖形模式需要的連結 if self.displayMode == 'g': stdoutLog.info (_("Starting graphical installation.")) try: from gui import InstallInterface except Exception, e: from flags import flags stdoutLog.error("Exception starting GUI installer: %s" %(e,)) # if we're not going to really go into GUI mode, we need to get # back to vc1 where the text install is going to pop up. if not flags.livecdInstall: isys.vtActivate (1) stdoutLog.warning("GUI installer startup failed, falling back to text mode.") self.displayMode = 't' if 'DISPLAY' in os.environ.keys(): del os.environ['DISPLAY'] time.sleep(2) if self.displayMode == 't': from text import InstallInterface if not os.environ.has_key("LANG"): os.environ["LANG"] = "en_US.UTF-8" if self.displayMode == 'c': from cmdline import InstallInterface self._intf = InstallInterface()# 建立InstallInterface執行個體 return self._intf
主要的工作包括建立dispatch執行個體,初始化介面,建立InstallInterface執行個體,它最後會建立InstallControlWindow執行個體,產生圖形介面。
整個Anaconda的運行流程如下圖:
圖2 Anaconda運行流程