Linux init程式分析

kunkliu發表於2020-04-05

轉載地址:  https://blog.csdn.net/zhoudaxia/article/details/6666872

  1、init程式剖析

    init程式是核心引導過程完成時建立的第一個程式。Linux使用了init程式來對組成Linux的服務和應用程式進行初始化。
    當 init 程式啟動時(使用傳統的sysvinit版本),它會開啟一個名為 /etc/inittab 的檔案。這個檔案是 init 的配置檔案,定義瞭如何對系統進行初始化。這個檔案還包含了有關出現電源故障時執行的操作(如果系統支援)、以及在檢測到 Ctrl-Alt-Delete 鍵序列時應該如何反應的資訊。請參看 清單 1 中該檔案的簡短片段,瞭解它所提供的內容。
    inittab 配置檔案使用通用格式定義了幾項內容:id:runlevels:action:process。其中 id 是惟一標識該項的字元序列。runlevels 定義了操作所使用的執行級別。action 指定了要執行的特定操作。最後,process 定義了要執行的程式。

清單 1. inittab 檔案摘錄

  1. # The default runlevel
  2. id:2:initdefault
  3. # Boot-time system configuration/initialization script
  4. si::sysinit:/etc/init.d/rcS
  5. # Runlevels
  6. l0:0:wait:/etc/init.d/rc 0
  7. l1:1:wait:/etc/init.d/rc 1
  8. l2:2:wait:/etc/init.d/rc 2
  9. l3:3:wait:/etc/init.d/rc 3
  10. l4:4:wait:/etc/init.d/rc 4
  11. l5:5:wait:/etc/init.d/rc 5
  12. l6:6:wait:/etc/init.d/rc 6
  13. z6:6:respawn:/sbin/sulogin
  14. # How to react to ctrl-alt-del
  15. ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
    在 init 載入 /etc/inittab 之後,就會將系統切換到 initdefault 操作所定義的執行級別。如 清單 1 所示,即執行級別 2。我們可以將執行級別看作是系統的狀態。例如,執行級別 0 定義了系統掛起狀態,執行級別 1 是單使用者模式。執行級別 2 到 5 是多使用者狀態,執行級別 6 表示重啟。(注意有些發行版對於執行級別的表示是不同的)。還可以以另一種方式考慮執行級別,即它是一種定義可以執行哪些程式(定義系統狀態的程式)的方法。
    我們可以使用 telinit 工具(這是一個指向 init 工具的連結)與 init 程式進行通訊。例如,如果目前在多使用者模式下(runlevel 2),希望切換到單使用者模式(runlevel 1),使用命令 telinit 1 即可(使用超級使用者模式)。要檢視系統的當前執行級別,請使用命令 runlevel。
    正如 清單 1 定義的一樣, initdefault 指定預設的 init 級別是 2 (多使用者模式)。在定義初始的執行級別之後,則呼叫 rc 指令碼以及引數 2(執行級別)來啟動系統。這個指令碼然後會呼叫各種服務和應用程式指令碼來啟動或停止特定的元素。在本例中,檔案都是在 /etc/rc2.d/ 中定義的。例如,如果要啟動 MySQL 應用程式(例如系統啟動),可以這樣呼叫:/etc/rc2.d/S20mysql start。在關閉系統時,則使用 stop 引數呼叫相同的指令碼集。
    最後,序列執行大量的指令碼以啟動各種需要的服務(通常可以在 Linux 的引導螢幕中看到)。即使這些服務彼此無關時,依然會順次啟動它們。其結果是引導過程非常耗時(尤其在具有很多服務的大型系統上更是如此)。
    關於這個問題的一個很明顯的解決方案是去掉 init 命令的序列特性,將其替換成並行化操作。在很多多處理系統中都可以看到這種用法。例如,socket striping,或者使用兩個或多個 socket 並行地移動資料,就是一個基於這個主題的解決方案。獨立磁碟冗餘陣列(RAID)系統也是通過將磁碟分成條狀(通常是並行的)來提高 I/O 效能。
    修改初始化程式非常的簡單。在引導時(使用 LILO 或 GRUB),指定一個新程式來開始處理系統初始化。指定 init=/sbin/mynewinit 作為核心引導行的一部分從而呼叫這個程式,而不是預設的 init 程式。在 /init/main.c:kernel_init()-->init_post()的核心原始碼中您可以看到這種用法。如果在核心引導行中提供了一個 init 命令,引導時就會使用它。否則,核心就會嘗試啟動 4 個備選方法之一(第一個是 /sbin/init,接著是/etc/init, /bin/init, /bin/sh)。    
    init程式是由核心啟動的第一個也是惟一的一個使用者程式,它是後續所有程式的發起者,比如init程式啟動/bin/sh程式後,才能夠在控制檯上輸入各種命令。
    init執行的基本流程如下:
    (1)解析/etc/inittab:執行sysinit命令指定的程式,以前通常是/etc/init.d/rcS,在新版本的init程式中則通常是/etc/rc.d/rc.sysinit指令碼。
    (2)執行/etc/rc.d/rc.sysinit:這是由init執行的第一個指令碼,此步進行的工作包括配置網路、配置核心引數、掛載root檔案系統、檢查檔案系統、設定系統時鐘、配置機器、開啟交換空間等。
    (3)執行/etc/rc.d/rcX.d/[K...][S...]:根據定義的initdefault執行級別,執行對應wait命令指定的程式,這會執行對應目錄下的各個程式,並等待它們執行完。在rcX.d目錄下,首先終止K開頭的服務(用來關閉一個服務),然後啟動S開頭的服務(用來啟動一個服務)。對每一個執行級別來說,在/etc/rc.d子目錄中都有一個對應的下級目錄。這些執行級別的下級子目錄的命名方法為rcX.d, 其中X就是代表執行級別的數字。在各個執行級別的子目錄中,都建立有到/etc/rc.d/init.d子目錄中命令指令碼程式的符號連結,連結的名稱在K與S後有一個數字,表示執行順序,數字小的先執行,例如K01tog-pegasus、S00microcode_ctl。對以K開頭的指令碼執行時系統會傳遞stop引數,而S開頭的指令碼系統會傳遞start引數。   
    (4)執行/etc/rc.d/rc.local:Redhat中執行模式2,3,5都把/etc/rc.d/rc.local作為初始化指令碼中的最後一個檔案,所以使用者可以自己在這個檔案中新增一些需要在其他初始化工作之後,登陸之前執行的命令。
    (5)執行getty程式:為每個聯機終端使用fork()建立一個子程式,並在子程式中執行getty程式,init程式則呼叫wait(),進入等待子程式結束狀態。getty程式設定終端型別、屬性、速度和線路規程等。對於字元介面的執行級別(如級別2和3),它會開啟並初始化一個tty埠,顯示提示資訊。通常,若/etc/issue文字檔案存在,則getty會首先顯示其中的文字資訊,然後顯示登入提示資訊(例如“plinux login:” ),出現字元登入介面,並等待使用者鍵入使用者名稱和口令。可以在inittab檔案中配置使用哪一種getty程式(在“id:runlevels:action:process”的process部分指定,並可以傳遞相應的getty引數),如agetty, getty, mgetty, uugetty, mingetty,fbgetty等。getty程式只能由超級使用者執行。
    注意如果第1步中的inittab檔案指定的預設執行級別是圖形使用者介面形式(如級別5),則init程式會轉向去執行/etc/X11/prefdm指令碼,它會執行/usr/sbin/gdm,啟動圖形登入介面。GDM管理的不只是X的啟動,還有登入,登出,掛起等一系列操作。
    啟動登入介面(圖形或字元介面),並輸入完使用者名稱後,getty會呼叫login程式。
    (6)執行login程式:getty呼叫exec()執行login程式,以核對輸入的使用者名稱和口令。由於呼叫了exec(而不是fork),login的執行環境會覆蓋getty的執行環境。login程式會讀取/etc/passwd,以使用者名稱和口令。login根據使用者輸入的使用者名稱,從口令檔案passwd中取得對應使用者的登入項,然後呼叫getpass()以顯示”password:”提示資訊,讀取使用者鍵入的密碼,然後使用加密演算法對鍵入的密碼進行加密處理,並與口令檔案中該使用者項中pw_passwd欄位作比較。如果使用者幾次鍵入的密碼均無效,則login程式會以出錯碼1退出執行,表示此次登入過程失敗。此時父程式(程式init)的wait()會返回該退出程式的pid,因此會根據記錄下來的資訊再次建立一個子程式,並在該子程式中針對該終端裝置再次執行getty程式,重複上述過程。
    如果使用者鍵入的密碼正確,則login就會把當前工作目錄(Currend Work Directory)修改成口令檔案中指定的起始工作目錄。並把對該終端裝置的訪問許可權修改成使用者讀/寫和組寫,設定程式的組ID。然後利用所得到的資訊初始化環境變數資訊,例如起始目錄(HOME=)、使用的shell程式(SHELL=)、使用者名稱(USER=和LOGNAME=)和系統執行程式的預設路徑序列(PATH=)。接著顯示/etc/motd檔案(message-of-the-day)中的文字資訊,並檢查並顯示該使用者是否有郵件的資訊。最後login程式改變成登入使用者的使用者ID,並執行口令檔案中該使用者項中指定的shell程式,如/bin/bash或/bin/csh等。有關login程式的一些執行選項和特殊訪問限制的說明,可參見Linux系統中的線上手冊頁(man -8 login)。
    (7)執行shell程式或x-windows:如果使用者名稱和口令正確,login呼叫exec執行shell命令列解釋程式(當然,也可以執行X-windows的圖形介面,如果使用者設定了的話)。登入shell會首先從/etc/profile檔案以及$HOME/.bash_profile檔案(或.bashrc檔案,若存在的話)讀取命令並執行。因此使用者可以把每次登入時都要執行的命令放在.bash_profile檔案中。如果在進入shell時設定了ENV環境變數(或者在.bash_profile檔案中設定了該變數),則shell還會從$ENV指定的檔案中讀去命令並執行。因此我們也可以把每次執行shell都要執行的命令放在ENV變數指定的檔案中。設定ENV環境變數的方法是把下列語句放在你起始目錄的.bash_profile檔案中: ENV=$HOME/.anyfilename; export ENV。
    執行shell時,原來的getty程式最終被替換成了bash程式,對應的getty,login,bash這三個程式也就具有相同的程式ID。在成功登入到Linux系統後,你會發現(使用”top”或”ps –ax”命令)自己終端原來的getty程式已經找不到了。因為getty程式執行了login程式,被替換成了login程式,並且最後被替換成你的登入shell程式。對於圖形使用者介面,login程式最後會被替換成圖形介面程式(如gnome-session程式)。
    (8)Linux執行時:init程式會負責收取孤兒程式。如果某個程式建立子程式之後,在子程式終止之前終止,則子程式成為孤兒程式。在Linux中所有的程式必須屬於單棵程式樹,所以孤立程式必須被收取。一旦程式成為孤兒,它會立即成為init程式的子程式。這是為了保持程式樹的完整性。
    (9)使用者登出:當某個終端或虛擬控制檯上的使用者登出之後,該終端上的所有程式都會被終止(killed),包括bash。然後,init程式就會呼叫fork為該終端或虛擬控制檯重新建立一個getty程式,以便能夠讓其他使用者登入。這是為什麼呢?你應該發現,當使用者登入時,“getty”用的是“exec”而不是“fork”系統呼叫來執行“login”,這樣,“login”在執行的時候會覆蓋“getty”的執行環境(同理,使用者註冊成功後,“login”的執行環境也會被shell佔用)。所以,如果想再次使用同一終端,必須再啟動一個“getty”。對於圖形介面,使用者登出後會回到圖形登入介面。
    (10)系統關閉:init負責殺死所有其它的程式,解除安裝所有的檔案系統並停止處理器的工作,以及任何其它被配置成要做的工作。 
    以Fedora 14桌面系統中為例(它使用新的upstart init程式,不過它相容sysvinit),/etc/inittab檔案內容如下:

  1. # inittab is only used by upstart for the default runlevel.
  2. #
  3. # ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
  4. #
  5. # System initialization is started by /etc/init/rcS.conf
  6. #
  7. # Individual runlevels are started by /etc/init/rc.conf
  8. #
  9. # Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf
  10. #
  11. # Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
  12. # with configuration in /etc/sysconfig/init.
  13. #
  14. # For information on how to write upstart event handlers, or how
  15. # upstart works, see init(5), init(8), and initctl(8).
  16. #
  17. # Default runlevel. The runlevels used are:
  18. # 0 - halt (Do NOT set initdefault to this)
  19. # 1 - Single user mode
  20. # 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
  21. # 3 - Full multiuser mode
  22. # 4 - unused
  23. # 5 - X11
  24. # 6 - reboot (Do NOT set initdefault to this)
  25. #
  26. id:5:initdefault:
    Fedora的預設執行級別為5,是多使用者的x-windows圖形介面。與傳統的sysvinit有所不同,在upstart中,只會為預設執行級別使用inittab檔案,要新增其他的執行級別,應該放到/etc/init/rc.conf中,而不是inittab中。upstart系統現在首先執行的是/etc/init/rcS.conf,其內容如下(Fedora 14中):

  1. start on startup
  2. stop on runlevel
  3. task
  4. # Note: there can be no previous runlevel here, if we have one it's bad
  5. # information (we enter rc1 not rcS for maintenance). Run /etc/rc.d/rc
  6. # without information so that it defaults to previous=N runlevel=S.
  7. console output
  8. exec /etc/rc.d/rc.sysinit
  9. post-stop script
  10. if [ "$UPSTART_EVENTS" = "startup" ]; then
  11. [ -f /etc/inittab ] && runlevel=$(/bin/awk -F ':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }' /etc/inittab)
  12. [ -z "$runlevel" ] && runlevel="3"
  13. for t in $(cat /proc/cmdline); do
  14. case $t in
  15. -s|single|S|s) runlevel="S" ;;
  16. [1-9]) runlevel="$t" ;;
  17. esac
  18. done
  19. exec telinit $runlevel
  20. fi
  21. end script
    upstart首先在系統引導時首先執行rc.sysinit指令碼,然後搜尋inittab的initdefault,用telinit切換到initdefault的級別來執行。upstart把原來/etc/inittab的功能被分散到/etc/init下的各個conf檔案中。
    注意不同的Linux發行版會對upstart做一些不同的定製。在ubuntu中,甚至預設不再有/etc/inittab檔案了。當然他仍然會處理這個檔案(如果有的話),如果你有需要,可以建立這個檔案,新增需要的內容。Ubuntu 10.04中的/etc/init/rcS.conf內容如下:
  1. # rcS - System V single-user mode compatibility
  2. #
  3. # This task handles the old System V-style single-user mode, this is
  4. # distinct from the other runlevels since running the rc script would
  5. # be bad.
  6. description "System V single-user mode compatibility"
  7. author "Scott James Remnant <scott@netsplit.com>"
  8. start on runlevel S
  9. stop on runlevel [!S]
  10. console owner
  11. script
  12. if [ -x /usr/share/recovery-mode/recovery-menu ]; then
  13. exec /usr/share/recovery-mode/recovery-menu
  14. else
  15. exec /sbin/sulogin
  16. fi
  17. end script
  18. post-stop script
  19. # Don't switch runlevels if we were stopped by an event, since that
  20. # means we're already switching runlevels
  21. if [ -n "${UPSTART_STOP_EVENTS}" ]
  22. then
  23. exit 0
  24. fi
  25. # Switch, passing a magic flag
  26. start --no-wait rc-sysinit FROM_SINGLE_USER_MODE=y
  27. end script
    這裡先做一些前期檢查,與Fedora不同的是,第一個執行的指令碼換成了/etc/init/rc-sysinit.conf,其內容如下:
  1. # rc-sysinit - System V initialisation compatibility
  2. #
  3. # This task runs the old System V-style system initialisation scripts,
  4. # and enters the default runlevel when finished.
  5. description "System V initialisation compatibility"
  6. author "Scott James Remnant <scott@netsplit.com>"
  7. start on filesystem and net-device-up IFACE=lo
  8. stop on runlevel
  9. # Default runlevel, this may be overriden on the kernel command-line
  10. # or by faking an old /etc/inittab entry
  11. env DEFAULT_RUNLEVEL=2
  12. # There can be no previous runlevel here, but there might be old
  13. # information in /var/run/utmp that we pick up, and we don't want
  14. # that.
  15. #
  16. # These override that
  17. env RUNLEVEL=
  18. env PREVLEVEL=
  19. console output
  20. env INIT_VERBOSE
  21. task
  22. script
  23. # Check for default runlevel in /etc/inittab
  24. if [ -r /etc/inittab ]
  25. then
  26. eval "$(sed -nre 's/^[^#][^:]*:([0-6sS]):initdefault:.*/DEFAULT_RUNLEVEL="\1";/p' /etc/inittab || true)"
  27. fi
  28. # Check kernel command-line for typical arguments
  29. for ARG in $(cat /proc/cmdline)
  30. do
  31. case "${ARG}" in
  32. -b|emergency)
  33. # Emergency shell
  34. [ -n "${FROM_SINGLE_USER_MODE}" ] || sulogin
  35. ;;
  36. [0123456sS])
  37. # Override runlevel
  38. DEFAULT_RUNLEVEL="${ARG}"
  39. ;;
  40. -s|single)
  41. # Single user mode
  42. [ -n "${FROM_SINGLE_USER_MODE}" ] || DEFAULT_RUNLEVEL=S
  43. ;;
  44. esac
  45. done
  46. # Run the system initialisation scripts
  47. [ -n "${FROM_SINGLE_USER_MODE}" ] || /etc/init.d/rcS
  48. # Switch into the default runlevel
  49. telinit "${DEFAULT_RUNLEVEL}"
  50. end script
    可見Ubuntu是在rc-sysinit.conf中才處理inittab並切換到initdefault級別來執行。

    init的完整初始化過程如下(包括啟動字元介面和圖形介面):

  1. /sbin/init
  2. --->/etc/init/rcS.conf
  3. --->exec /etc/rc.d/rc.sysinit 執行第一個指令碼(Ubuntu中為/etc/init/rc-sysinit.conf)
  4. --->/bin/hostname 獲取主機名(設定$HOSTNAME)
  5. --->/etc/sysconfig/network 配置網路基本引數
  6. --->/proc/mounts 檢測並掛載procfs,sysfs到/proc,/sys
  7. --->/etc/init.d/functions  包含一些通用函式,會被/etc/init.d(是到rc.d/init.d的連結)下的指令碼用到
  8. --->/etc/sysconfig/i18n 設定終端字符集
  9. --->/etc/sysconfig/init 設定終端和圖形介面的一些引數
  10. --->deamon(),killproc(),pidofproc() 一些通用函式
  11. --->status(),echo_success(),
  12. --->update_boot_stage(),strstr()
  13. --->/selinux/enforce 檢查SELinux的狀態
  14. --->/etc/system-release 列印熟悉的發行版資訊 “Welcome to Fedora ..."
  15. --->/proc/cmdline 獲取核心啟動的命令列引數
  16. --->/proc/sys/kernel/modprobe 獲取modprobe的位置(為/sbin/modprobe)
  17. --->/sbin/sysctl 初始化硬體(通過sysctl設定執行時核心引數)
  18. --->kill $nashpid 殺死所有的nash程式(我們在initrd中使用的shell)
  19. --->/sbin/start_udev 啟動udev((動態裝置管理程式)
  20. --->/bin/taskset  設定程式的預設CPU親合值(即優先使用哪個CPU,用在多處理器環境中)
  21. --->/etc/sysconfig/modules/*.modules 載入其他使用者自定義的模組
  22. --->sysctl -e -p /etc/sysctl.conf 配置核心引數
  23. --->/proc/devices 獲取裝置號及相應裝置名,以便進行裝置初始化
  24. --->/sbin/dmraid 啟用software raid
  25. --->/sbin/kpartx “/dev/mapper/..."  為software raid上的每塊硬碟建立裝置對映
  26. --->/.autofsck 是否自動執行檔案系統檢查
  27. --->sulogin 若為單使用者模式,執行單使用者登入程式
  28. --->plymouth --show-splash 顯示啟動時的背景畫面
  29. --->/etc/sysconfig/readonly-root 設定root檔案系統掛載方式
  30. --->從/etc/fstab掛載暫存裝置
  31. --->/etc/rwtab, /etc/rwtab.d/* 掛載其他有卷標的分割槽
  32. --->ip addr show 獲取並設定網上ip地址
  33. --->從/etc/fstab掛載持久資料的儲存裝置
  34. --->/etc/statetab, /etc/statetab.d/* 持載其他持久資料的儲存裝置
  35. --->/sbin/fsck 檢查檔案系統
  36. --->umount -a & reboot -f 如果檢查失敗,解除安裝檔案系統並重啟
  37. --->以讀寫方式重新掛載root檔案系統 如果檔案系統檢查沒有失敗
  38. --->掛載所有其他的檔案系統
  39. --->cat /var/lib/random-seed > /dev/urandom 初始化偽隨機數生成器
  40. --->/usr/sbin/system-config-keyboard,passwd,... 配置機器相關引數(如果有需要的話)
  41. --->/etc/sysconfig/network 重新讀取網路配置資料,並重設hostname
  42. --->清除相關的/, /var,/tmp資料
  43. --->/sbin/swapon 開啟各個交換區分(根據/proc/swaps)
  44. --->/usr/sbin/system-config-network-cmd   執行引導時的網路配置(傳遞核心啟動的netprofile引數)
  45. --->dmesg -s 131072 > /var/log/dmesg 轉儲核心啟動的訊息資訊
  46. --->/etc/inittab
  47. --->id:5:initdefault: 查詢initdefault定義的執行級別(為5,圖形使用者介面)
  48. --->telinit $runlevel 切換到對應級別執行
  49. --->/etc/init/rc.conf
  50. --->exec /etc/rc.d/rc $RUNLEVEL
  51. --->/etc/profile.d/lang.sh 設定語言環境
  52. --->/etc/rc.d/rc5.d/KNxxxx 先關閉相關服務(在關閉系統時也會執行)
  53. --->/etc/rc.d/init.d/xxxx
  54. --->/etc/rc.d/rc5.d/SNxxxx 再開啟相關服務
  55. --->etc/rc.d/rc5.d/xxxx
  56. --->/etc/rc.d/rc.local  在所有init指令碼執行完之後執行,可在些新增自己的初始化命令(Ubuntu中為/etc/rc.local)
  57. --->/etc/init/start-ttys.conf 啟動tty1-tty6裝置
  58. --->/etc/sysconfig/init 指定tty裝置,通常為/dev/tty1-/dev/tty6
  59. --->/etc/init/tty.conf
  60. --->exec /sbin/mingetty $TTY 在每個tty裝置上啟動mingetty
  61. --->成功後就可以通過Ctrl+Alt+F1..F6在各個不同的tty之間切換
  62. ################################################# 字元介面 ################################################
  63. --->fork()--->/sbin/mingetty 執行mingetty程式,出現字元登入介面
  64. --->/etc/issue 在登入介面上顯示發行版資訊
  65. --->exec("/bin/login",...) 執行/bin/login程式,驗證使用者名稱和口令
  66. --->/etc/passwd 讀取passwd檔案核對使用者名稱和口令
  67. --->jackzhou:x:500:500:jackzhou:/home/jackzhou:/bin/bash
  68. --->切換到工作目錄/home/jackzhou
  69. --->初始化環境變數$HOME,$PATH等
  70. --->/etc/motd 顯示當天的訊息
  71. --->檢查新郵件
  72. --->exec("/bin/bash",...) 執行bash程式
  73. --->/etc/profile 執行這些指令碼中的命令
  74. --->.bash_profile或.bashrc
  75. --->ENV=$HOME/.anyfilename; export ENV  執行$ENV指向的指令碼(如果設定了的話)
  76. --->bash執行中 mingetty,login最後替換成了bash,登入成功
  77. ################################################## 圖形介面 #############################################
  78. --->/etc/init/prefdm.conf
  79. --->exec /etc/X11/prefdm -nodaemon 準備啟動指定的X圖形介面(X Display Manager)
  80. --->/etc/sysconfig/i18n 設定語言環境
  81. --->/etc/sysconfig/desktop 讀取指定的DM配置(如果有的話)
  82. --->exec /usr/sbin/gdm 啟動指定的DM(gdm, kdm, wdm或xdm,預設為/usr/sbin/gdm)
  83. --->啟動X server視窗
  84. --->/etc/gdm/custom.conf 根據配置在X視窗中顯示登入介面
  85. --->使用者選擇語言、鍵盤佈局、會話等
  86. --->/usr/share/xsessions/gnome.desktop 讀取會話要顯示的名稱
  87. --->Exec=gnome-session 指定預設的會話程式
  88. --->使用者輸入使用者名稱和密碼
  89. --->用/bin/login驗證使用者名稱和密碼
  90. --->/etc/gdm/PreSession/* 執行會話前的一些任務(比如更改X視窗的預設背景)
  91. --->/etc/gdm/PostLogin/* 執行一些登入後立即需要執行的命令
  92. --->/etc/gdm/Xsession gnome-session--->/etc/X11/xinit/Xsession 啟動GNOME會話
  93. --->/etc/X11/xinit/xinitrc-common 匯入Xsession與xinitrc共用的程式碼
  94. --->/etc/profile.d/lang.sh 設定i18n環境
  95. --->/etc/X11/Xresources 讀取使用者登入時需要載入的全域性資源
  96. --->/etc/X11/Xmodmap  讀取的全域性的鍵盤配置(用於xdm和xinit,用startx啟動圖形介面時要用到)
  97. --->/etc/X11/xinit/xinitrc.d/* 執行所有的xinitrc指令碼
  98. --->exec -l $SHELL -c gnome-session  執行特定的環境設定(以前是執行./Xclients.d/Xclients.gnome-session.sh)
  99. --->/etc/X11/xinit/Xclients 執行各個X客戶端的指令碼(或者$HOME/.xsession,或者$HOME/.Xclients)
  100. --->/etc/sysconfig/desktop 讀取指定的會話程式配置(如果有的話)
  101. --->exec "$(type -p gnome-session)" 預設執行gnome-session,進入GNOME桌面
  102. --->GNOME桌面執行中 mingetty,login最後替換成了gnome程式,登入成功
  103. --->/etc/gdm/PostSession/* GNOME會話結束時執行的指令碼
  104. #################################### 在字元介面下通過startx啟動圖形介面 ##############################################
  105. --->/bin/bash 在字元介面的Shell下
  106. --->/usr/bin/startx
  107. --->記錄$HOME目錄和/etc/X11/xinit下的.xinitrc和.xserverrc檔案 以$HOME目錄下的為優先
  108. --->解析使用者指定的client、server、display引數及其選項
  109. --->沒有指定引數時就設為前面記錄的.xinitrc和.xserverrc檔案
  110. --->XAUTHORITY=$HOME/.Xauthority 設定XAUTHORITY環境變數
  111. --->設定X server的許可權資訊
  112. --->xinit $client $clientargs -- $server $display $serverargs 啟動X server和第一個X client
  113. --->/etc/X11/xinit/xinitrc 用來執行各個X client(上面沒有指定第一個client時)
  114. --->/etc/X11/xinit/xinitrc-common 匯入Xsession與xinitrc共用的程式碼
  115. --->/etc/profile.d/lang.sh 設定i18n環境
  116. --->/etc/X11/Xresources   讀取使用者登入時需要載入的全域性資源
  117. --->/etc/X11/Xmodmap 讀取全域性的鍵盤配置
  118. --->/etc/X11/xinit/xinitrc.d/* 執行所有的xinitrc指令碼
  119. --->/etc/X11/xinit/Xclients 執行各個X client的指令碼(或者$HOME/.Xclients)
  120. --->/etc/sysconfig/desktop 讀取指定的會話程式配置(如果有的話)
  121. --->exec "$(type -p gnome-session)" 預設執行gnome-session,進入GNOME桌面
  122. --->GNOME桌面執行中 mingetty,login最後替換成了gnome程式,登入成功
  123. --->/etc/gdm/PostSession/* GNOME會話結束時執行的指令碼

    注意在rc.sysinit載入完/etc/sysconfig/modules/中(如果你希望額外載入一些比如遙控器之類的模組,你可以在這裡增加指令碼)的使用者自定義的模組後,就會由update_boot_stage通知圖形化的啟動介面,準備進入啟動畫面,核心啟動的命令列引數(在grub中可以看到)中會指定rhgb程式。rhgb程式的作用是在啟動的時候建立一個臨時的僅使用loopback網路的X視窗伺服器,然後在這個視窗上顯示啟動進度,init程式的其他部分可以通過rhgb-client程式向這個進度視窗傳送訊息。在“配置機器相關引數”這一步中,如果存在/.unconfigured檔案,會先呼叫rhgb-client向進度視窗傳送訊息,然後呼叫/usr/bin/system-config-keyboard配置鍵盤,呼叫 /usr/bin/passwd root配置超級使用者密碼,呼叫/usr/sbin/system-config-network-tui配置網路,呼叫/usr/sbin/timeconfig配置時區,呼叫 /usr/sbin/authconfig-tui --nostart配置網路登入,呼叫/usr/sbin/ntsysv配置預設的執行級別。然後清空包括/var/lock/,/var/run/, /tmp等在內的臨時目錄,並開啟交換空間。執行完rc.sysinit後,rcS.conf就會查詢inittab中的預設執行級別並切換到這個級別。

    轉到rc.conf,它呼叫/etc/rc.d/rc指令碼,執行指定級別目錄下的各個啟動指令碼。首先按照名稱順序執行那些K打頭的指令碼,然後按照名稱順序執行那些S打頭的指令碼。啟動指令碼(是符號連結)中的數字是怎麼來的呢,它是由你指定的,如果你要增加自己的啟動指令碼到相應的啟動級別中去,這個數字當然應該由你指定,因為只有你才知道這個指令碼應該以什麼樣的優先順序啟動。但是對於那些已經存在的啟動指令碼,它們的優先順序是在指令碼中最前面的註釋行中的chkconfig這一行指定的,在這一行中,你可以看到類似# chkconfig: 35 99 95的字樣,它的含義是:這個指令碼應該增加到執行級別3和執行級別5中,啟動的優先順序是99,關閉的優先順序是95,當然,這些數字是由那些Fedora的開發者測試過沒有問題,所以才寫在這裡的。二進位制的程式/sbin/chkconfig將會讀取這一行,並且在將服務增加到啟動級別中去的時候自動生成檔名。檔名中的第一個字元S和K代表了什麼含義呢?它代表了你在services控制皮膚中選擇了開啟這個服務還是關閉這個服務,如果你在那裡開啟了這個服務,則以S作為前導符,否則為K。結合我們前面介紹的啟動過程,你就可以知道,在啟動的時候,Fedora會首先保證那些K打頭的指令碼是關閉的(通過以stop引數呼叫這個指令碼),然後才會逐個啟動那些S打頭的指令碼(通過以start引數呼叫這個指令碼)。對於每個啟動指令碼檔案,如果想知道啟動了時候都做了些什麼,可以檢視相應指令碼中的start()函式,比如對於avahi-dnsconfd這個指令碼,我們可以看到,它只是執行了/usr/sbin/avahi-dnsconfd -D這個命令。

    最後執行的初始化指令碼是rc.local,你可以在這個指令碼中新增自定義的需要啟動的服務或需要執行的命令。在所有需要啟動的服務都啟動完畢以後,rc指令碼通過rhgb-client程式通知rhgb圖形介面退出,rhgb的使命就完成了。
    init程式在執行完rc.local後,執行/etc/init/start-ttys.conf的配置,它查詢init配置檔案中的$ACTIVE_CONSOLES指定的每個tty裝置(為tty1-tty6),並呼叫tty.conf啟動這些tty裝置。tty.conf會在/dev/tty1-/dev/tty6裝置上啟動/sbin/mingetty。從現在開始,你可以通過Ctrl+Alt+F1..F6在各個不同的tty之間進行切換了。
    上面“fork()--->/sbin/mingetty”這一部分是指傳統字元介面的啟動及登入過程,這個過程在前面已經介紹比較清楚了。現在的Linux桌面系統都是登入到圖形使用者介面,登入螢幕是圖形介面的形式。下面“/etc/init/start-ttys.conf”這一部分是指圖形介面的啟動和登入過程。
    1)直接啟動圖形介面
    在啟動mingetty後,如果執行級別為5,init程式會執行prefdm.conf的配置,它呼叫/etc/X11/prefdm指令碼,準備啟動圖形介面登入管理器。prefdm將會讀取位於/etc/sysconfig/desktop中的配置檔案,如果沒有指定任何配置檔案,prefdm執行的順序依次為gdm,kdm,wdm和xdm,從而出現圖形登入介面。後面的啟動部分就屬於GNOME,KDE或者其它相應的視窗管理器了。
    注意,對於Ubuntu,預設執行級別為2,在/etc/rc2.d目錄中包含了啟動登入管理器gdm的指令碼。    
    無論是gdm,xdm還是kdm,所做的事情都是類似的,即啟動一個X server視窗,基於這個X視窗提供一個圖形化的使用者登入介面,以便在實際進入X視窗系統之前,對使用者進行驗證,並且提供使用者選擇自己希望的語言,視窗管理器等的機會。除此之外,dm程式一般還支援別的一些操作,比如提供直接關機的選項以及根據配置決定是否開啟XDMCP服務的埠等。gdm的配置定義在/etc/gdm/custom.conf中。
    XDMCP服務是X視窗顯示管理器控制協議的縮寫,它允許使用者在遠端電腦上執行X視窗服務,然後通過XDMCP協議使用本地的XDM登入,登入以後的後續操作將使用遠端的X視窗作為顯示系統。一個很簡單的例子,首先使用gdmsetup程式(系統管理選單中的登入視窗)開啟XDMCP的支援(遠端選項卡更改為與本地相同),然後開啟一個終端視窗,執行Xnest :1 -query 127.0.0.1命令(Xnest並不是預設安裝的命令),你將在一個新開的視窗中看到和你的登入螢幕一模一樣的登入螢幕,你可以登入其它使用者,進行所有和本地使用者一樣的操作。顯然如果你是在另外一臺電腦上,只需要把相應的ip地址改掉就可以了。並不一定非要使用Xnest程式,你甚至可以在遠端的Win32系統上進行基於XDMCP的遠端登入,這首先需要你在你的windows系統上執行一個X 視窗系統,有很多種類似的實現,包括X-win32和cygwin在內的各種免費和收費版本都是一個不錯的選擇,事實上,一臺強勁的伺服器通過這種方法可以將N臺落魄的486PC轉變成可以執行高階科學運算的X終端。除了這些方法,你還可以使用內建於gnome之中的vino程式,這個程式可以基於本地的X視窗開啟一個相容於vnc的服務,你可以使用各種型別的vncviewer來連線這個服務並進行遠端操作(參見首選項選單中的遠端桌面),這種實現方式下,遠端顯示的螢幕和本地螢幕是完全相同的。或者你也可以使用單獨的vncserver,這種使用方式和XDMCP的使用方法類似,只是登入的使用者和使用的視窗管理器都是由vncserver指定好的。
    在登入介面上,使用者可以選擇語言、鍵盤佈局、會話等。系統內建的幾個會話包括安全模式終端,安全模式gnome以及上一次的成功登入等,其它的會話則是從配置檔案中讀取的,gdm將會在多個目錄中尋找設定的會話,包括/etc/X11/sessions/,/usr/share/gdm/BuiltInSessions/,/usr/share/xsessions/等,路徑可以通過daemon/SessionDesktopDir配置項進行更改,gdm在這些目錄中尋找副檔名為desktop的檔案,比如預設會話對應的檔案是 /usr/share/gdm/BuiltInSessions/default.desktop,而gnome會話對應的檔案為/usr/share/xsessions/gnome.desktop。這些配置檔案定義了在不同的語言中這個會話要顯示的名稱。
    當使用者選擇了一個X會話,輸入了正確的使用者名稱和密碼以後,gdm執行命令的順序依次是,首先它將執行位於daemon/PreSessionScriptDir配置項路徑下(預設為/etc/gdm/PreSession/)的所有指令碼檔案,來執行啟動會話前的一些任務,比如更改X視窗的預設背景之類,然後它將呼叫位於daemon/PostLoginScriptDir配置的目錄中(預設為/etc/gdm/PostLogin)的指令碼,執行一些在剛剛登入以後需要執行的命令,然後它將以前面提到的desktop檔案中定義的exec引數的值作為引數,呼叫daemon/BaseXsession配置項指定的指令碼(預設為/etc/gdm/Xsession),比如如果你選擇的是預設會話,那麼執行的命令將會是/etc/gdm/Xsession default,如果你檢視這個檔案你將發現,在這種情況下,它將首先檢查是否存在主目錄的.xsession檔案,如果存在就執行它,否則檢查是否存在主目錄下的.Xclients檔案,如果存在則執行它,否則就將執行/etc/X11/xinit/Xclients檔案,這個檔案根據 /etc/sysconfig/desktop配置檔案中的設定執行各個X client,第一個X client預設為執行gnome-session。
    執行完gnome-session,我們就進入了GNOME桌面環境。而配置在daemon/PostSessionScriptDir配置項(預設值為/etc/gdm/PostSession/)所設定的目錄中的指令碼將在GNOME會話結束以後執行,這意味著無論出於什麼原因,gnome程式已經完全退出了,也許是你選擇了登出命令,也許是X視窗崩潰了,如果你有這方面的需要,可以將相應的指令碼放在對應的目錄中。
    2)通過startx啟動圖形介面
    還有一種啟動圖形介面的方式,就是在登入到字元介面後,通過執行startx指令碼來啟動X圖形介面。startx並不使用gdm來啟動X視窗,而是用xinit程式啟動X。/usr/bin/xinit是一個二進位制檔案,並非是一個指令碼。它的主要功能是啟動一個X伺服器,同時啟動第一個基於X的客戶端應用程式。當第一個Client退出時,xinit將殺死X server程式,然後自己終止執行。
    參考xinit的man文件,可知其用法為:xinit [[client] options ] [-- [server] [display] options]。其中client用於指定第一個X客戶端應用程式,client後面的options是傳給這個應用程式的引數,server是用於指定啟動哪個X伺服器,一般為/usr/bin/X或/usr/bin/Xorg,display用於指定display number,一般為0,表示第一個display,option為傳給server的引數。
    如果不指定client,xinit會查詢HOME(環境變數)目錄下的.xinitrc檔案,如果存在這個檔案,xinit直接呼叫execvp函式執行該檔案,以啟動指定的X客戶端程式。如果這個檔案不存在,那麼client及其options預設為: xterm -geometry +1+1 -n login -display :0 。
    如果不指定server,xinit會查詢HOME(環境變數)目錄下的.xserverrc 檔案,如果存在這個檔案,xinit直接呼叫execvp函式執行該檔案,以啟動指定的X伺服器。如果這個檔案不存在,那麼server及其display為: X :0 。如果系統目錄中不存在X命令,那麼我們需要在系統目錄下建立一個名為X的連結,使其指向真正的X server命令(Ubuntu下為Xorg)。
    下面是幾個關於xinit應用的例子:
    (1)xinit /usr/bin/xclock -- /usr/bin/X :0
    該例子將啟動X server, 同時將會啟動xclock。請注意指定client或server時,需要用絕對路徑,否則xinit將因無法區別是傳給xterm或server的引數還是指定的client或server而直接當成是引數處理。
    (2)在HOME下新建.xinitrc檔案,並加入以下幾行:
  1. xsetroot -solid gray &
  2. xclock -g 50x50-0+0 -bw 0 &
  3. xterm -g 80x24+0+0 &
  4. xterm -g 80x24+0-0 &
  5. twm
    當xinit啟動時,它會先啟動X server,然後啟動一個clock,兩個xterm,最後啟動視窗管理器twm。請注意最後一個命令不能後臺執行,否則所有命令都後臺執行的話xinit就會返回退出,同樣的,除最後一個命令外都必須後臺執行,否則後面的命令將只有在該命令退出後才能執行。
    看到這裡,眼尖的人或許早以看出xinit的功能完全可以由指令碼來實現,例如要啟動X Server和一個xterm,就像xinit預設啟動的那樣,只需要在新建一個指令碼或在rc.local中加入:
  1. X&
  2. export DISPLAY=:0.0
  3. xterm
    這個實現完全正確,然而卻並沒有完全實現xinit所具有的功能,xinit的功能就是當最後一個啟動的client(如上面第二個例子中的twm視窗管理器)退出後,X伺服器也會退出。而我們的指令碼實現中當我們退出xterm後並不會退出X server。
    startx可以看作是xinit的前端,用法和xinit的基本一樣:startx [[client] options ] [-- [server] [display] options]。為什麼呢?這是因為startx其實就是一個指令碼,它啟動X server就是通過呼叫xinit命令實現的,startx的引數將全部傳給xinit。因此,這些引數的意義和xinit的引數是一樣的。
    下面是兩個關於startx命令的簡單例子:
    (1)startx -- -depth 16:以16位色啟動X 伺服器。
    (2)startx -- -dpi 100:以100的dpi啟動X 伺服器。
    startx會先記錄$HOME下的.xinitrc和.xserverrc檔案(如果有的話),系統目錄/etc/X11/xinit/下的xinitrc和.xserverrc檔案。然後解析使用者的引數,如果該使用者指定了該引數,那麼startx就會以該引數來啟動xinit,否則就會解析$HOME目錄下的.xinitrc和.xserverrc檔案,如果該檔案不存在,就會解析系統目錄下(/etc/X11/xinit/)的xinitrc和xserverrc檔案,如果這個檔案也不存在,那 startx就將以預設的client(xterm)和server(/usr/bin/X)為引數來啟動xinit。

    在Fedora 14中,只有/etc/X11/xinit/xinitrc檔案,由它來執行Xclients指令碼,這個指令碼用於執行各個指定的X client,其中的第一個X client即為gnome-session,這就是GNOME桌面環境。從程式碼可知,xinitrc的功能與Xsession幾乎一樣,只有一些細微的差別(在Ubuntu中xinitrc是直接呼叫Xsession的)。

     完整的Linux init程式啟動過程如下圖:

圖1 Linux init程式啟動過程

    2、initng介紹
    由於傳統的init程式(sysvinit)是一個序列化的程式,因此可對這部分系統進行充分優化。實際上,您可以使用任何方法來對init程式進行優化。其中最簡單方法是禁用不必要的服務。例如,如果您執行的是一個桌面系統(而不是一個伺服器),就可以禁用諸如 apache、sendmail 和 mysql 之類的服務,這樣可以縮短init序列。
    其他的一些init程式版本解決了這個問題。一種是基於依賴關係的(即使用依賴關係來提供並行化,如新版的initng),一種是一個基於事件的系統(即程式依賴於事件來表示自己何時啟動或停止,如upstart)。
    initng(下一代 init)可以完全取代非同步啟動程式的init,它能更加快速地完成init程式。initng 背後的基本思想是隻要滿足了服務的依賴關係就可啟動。這樣系統就可以在 CPU 和 I/O 之間實現較好的平衡。當從磁碟上載入一個指令碼或等待硬體裝置啟動的同時,可以執行另一個指令碼來啟動另外一個服務。
    作為一個基於依賴關係的解決方案,initng 使用自己的初始化指令碼集,它們對服務和守護程式的依賴性進行了編碼。清單 2 展示了一個示例。這個指令碼指定了需要為給定的執行級別啟動的服務。該服務具有兩個依賴關係,使用 need 關鍵字定義,分別是 system/initial 和 net/all。在 system/my_service 可以啟動之前,這些服務必須是可用的。當這些服務可用時,exec 關鍵字就開始起作用了。exec 關鍵字(以及 start 選項)定義瞭如何使用任何可用的選項啟動服務。要停止這個服務,就會使用 exec 關鍵字以及 stop 選項。
清單 2. 為 initng 定義服務
  1. service system/my_service {
  2. need = system/initial net/all;
  3. exec start = /sbin/my_service --start --option;
  4. exec stop = /sbin/my_service --stop --option;
  5. }
    您可以使用服務定義對整個系統進行編碼,如清單 2 所示。那些沒有依賴關係的服務可以立即(並行地)啟動,而具有依賴關係的服務則必須等待以安全啟動。您可以將 initng 看作一個基於目標的系統。其目標就是要啟動的服務。沒有進行顯式的規劃;相反,依賴關係簡單地定義了服務初始化的流程,這個過程中隱含著並行化的操作。
    initng 的典型安裝需要 initng 發行版(原始碼或二進位制檔案)和 ifiles 發行版。您可以使用 ./configure、make 和 make install 編譯自己的 initng 發行版。您必須使用 cmake 來編譯 ifiles 檔案(這是指令碼檔案)。根據系統需求的不同,您可能需要建立新的服務/守護程式定義(不過很可能 initng 社群中已經有人這樣做了)。然後您還必須修改 LILO 或 GRUB 的配置以指向新的 /sbin/initng。要控制 initng,需要使用 ngc(對應telinit 與傳統的 init)。它們的語法有些不同,不過功能是相同的。

相關文章