本来想写篇关于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. className = %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运行流程