Linux系統架構和應用技巧_第1頁
Linux系統架構和應用技巧_第2頁
Linux系統架構和應用技巧_第3頁
Linux系統架構和應用技巧_第4頁
Linux系統架構和應用技巧_第5頁
已閱讀5頁,還剩257頁未讀, 繼續免費閱讀

付費下載

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

Linux系統架構和應用技巧目錄\h第1章你必須知道的!Linux內部結構\h1.1Linux的三大基礎:磁盤、進程、內存\h1.2磁盤和文件\h1.2.1磁盤的3D參數\h1.2.2新舊分區表\h1.2.3文件系統和I/O子系統\h1.3控制進程就等于控制Linux\h1.3.1fork和exec分別是進程的分身和變身\h1.3.2作業控制中的各項任務處理\h第2章別說缺少機器!虛擬化基礎設施環境的構建\h2.1基礎設施工程師的成長來自于日常積累\h2.2LinuxKVM虛擬網絡\h2.2.1虛擬網絡的構建和虛擬機的配置\h2.2.2DNS服務器的搭建\h2.2.3郵件服務器的建立\h2.3HA集群環境在虛擬機上的實現\h2.3.1對HAAdd-on的理解\h2.3.2Linux主機的準備以及虛擬機的構建\h2.3.3HAAdd-On的導入和配置\h2.3.4HA集群設計及運用的準備\h第3章10輪決勝!在自編的腳本中靈活使用命令\h3.1簡單有效的Shell腳本\h3.2Shell腳本的基本規則\h3.2.1Shell腳本的操作確認\h3.2.2引號的使用方法\h3.2.3條件判斷的寫法\h3.2.4數組和位置參數的使用方法\h3.2.5命令置換和數值演算\h3.3用Shell腳本一決勝負\h3.3.1[第1輪對決]在跳板服務器上學到的秘籍~基本模式+異常處理\h3.3.2[第2輪對決]在分布式Shell上學到的秘籍~基本模式+管道\h3.3.3[第3輪對決]在進程監視中學到的秘籍~狀態遷移處理\h3.3.4[第4輪對決]秘籍外傳~由make命令進行簡單的批處理\h3.3.5[第5輪對決]從模擬快照(snapshot)學到的秘籍~用管道操作日志\h3.3.6[第6輪對決]在云備份中學到的秘籍~在思考實驗中組合處理流程\h3.4Perl腳本的對決\h3.4.1[第7輪對決]自己編寫Perl的樂趣\h3.4.2[第8輪對決]通過Tweet體驗Perl帶來的便利\h3.4.3[第9輪對決]用進程監控掌握fork\h3.4.4[第10輪對決]終極秘籍Perl與管道的結合\h第4章最后的堡壘!內核源代碼的閱讀\h4.1閱讀源代碼\h4.2內核源代碼的走讀方法\h4.2.1Linux內核的構建步驟\h4.2.2內核源代碼探索入門\h4.2.3讀懂結構體和指針\h4.3探索核心子系統\h4.3.1進程管理子系統\h4.3.2內存管理子系統\h4.4內核源代碼的分析實例\h4.4.1Linux內核的系統時間\h4.4.2閏秒發生的瞬間\h4.4.3進一步探索的指南\h第5章先行一步!RHEL6新功能綜述\h5.1支持商品化硬件的“操作系統進化”\h5.1.1ext4文件系統的采用\h5.1.2NetworkManager服務的引入\h5.1.3用dracut創建初始RAM磁盤\h5.1.4通過anacron實現定期任務執行\h5.2對服務器啟動處理進行變革的Upstart\h5.2.1Upstart的概要\h5.2.2Upstart任務的創建示例\h5.3用ControlGroups控制資源分配\h5.3.1ControlGroup的概要\h5.3.2各子系統的主要參數\h5.3.3cg命令群的管理\h5.3.4虛擬機的cgroups操作\h5.4通過LXC體驗容器型虛擬化技術\h5.4.1容器型虛擬化技術的概要\h5.4.2容器中Web服務器的啟動\h5.4.3其他的容器設置第1章你必須知道的!Linux內部結構1.1Linux的三大基礎:磁盤、進程、內存Linux工程師的工作有時需要登錄陌生的Linux服務器才能進行。在這種時候,起初筆者會用到df、ps和free這三個命令,倒并非出于刻意,只是本能地去執行這些指令。例如,當你接到一個緊急事件的請求響應,而事先又不知道該服務器的配置信息時,就得首先用這三個命令檢查一下這臺服務器的狀態,并對當前資源的使用情況進行確認。11此外,通過w命令來確認是否有用戶在同一時間在同一臺服務器上工作,也是非常重要的。首先,df命令主要用來檢查文件系統上的可用空間。雖然服務器在運行過程中通常會對文件系統的使用率進行監測,但在進行設備維護而暫時停止監測時,就很容易導致意外情況的發生。比如曾發生過這樣的嚴重狀況:當被告知“某個命令無法順利執行”時,試著輸入df指令,竟發現此時根文件系統的利用率已高達100%。因此,在登錄服務器進行工作時,還是養成時常用df命令來檢查文件系統使用率的習慣吧。df命令也可用于確認磁盤分區的構成以及數據的分配狀況,還可以確認用于保存數據的文件系統有多少千兆字節,或是否有NFS安裝區域等。了解應用系統的使用數據是如何被組織和存儲的,是全面了解服務器運行的一個關鍵。經典的軟件開發著作《人月神話》[1]中寫道:“光看流程圖不看數據表只是徒勞,但看了數據表,流程圖便不看也罷?!盜T,顧名思義,是指信息技術,其核心自然是信息(數據)的處理,因此數據可以說是所有操作的基本了。此外,服務器是利用進程來處理數據的,而ps命令則可以被用來確認當前服務器上運行進程的狀態。如果平常我們能接觸到各種ps命令的輸出,那么只需看看進程的名稱,就可以大致了解這臺服務器的用途和設置。有時會發現一些意料之外的進程正在運行,而經過仔細確認,可能就會在不經意間找到問題的原因。最后的free命令適用于確認存儲的使用狀況。通過ps命令所得到的進程的信息,再結合磁盤、高速緩存和內存的使用情況,能很好地掌握服務器的運行狀態,了解這是一個“重載”到什么程度的服務器。TechnicalNotes[1]《人月神話》弗雷德里克·布魯克斯(著),汪穎(譯),清華大學出版社,2007所謂的“重載”“輕載”,是一種含糊的說法,實際上是結合過去CPU的使用率和磁盤I/O的頻率來進行綜合判斷的。然而,CPU或I/O操作與內存的使用情況是密切相關的。如同醫生可以從患者的外部情況推斷病情一樣,通過內存的使用情況,就能推斷出包括CPU和I/O在內的整個服務器的運行狀態。即便在CPU的使用率和磁盤I/O的頻率的歷史數據十分翔實的情況下,也應時刻質疑“莫非是CPU使用率太高了?”“也許是I/O負載太高了?”,以這樣的態度去觀察數據,方能分析出最接近真實狀況的結果。雖然這個引言稍嫌冗長,但通過以上例子,可以總結出三個應了解的Linux的基本分支,即用于保存數據的磁盤、實際處理數據的進程,以及存儲服務器的各種運行信息的內存。針對以上三點,本章將從Linux內部結構的角度出發,介紹有助于實踐應用的知識。1.2磁盤和文件1.2.1磁盤的3D參數Linux這一類操作系統的任務是隱藏物理硬件信息,即對用戶和上層應用屏蔽底層硬件的差異,并提供統一的操作方法。但是,對于磁盤裝置,還是需要讓人知道它的物理構造的。這一點似乎常常被誤導。例如,在筆者的RedHatEnterpriseLinux6(RHEL6)測試機上運行fdisk指令,就能得到如下結果。#fdisk-l/dev/sda

磁盤/dev/sda:500.1GB,500107862016字節

磁頭255,扇區63,柱面60801

Units=柱面數of16065*512=8225280字節

扇區大小(邏輯/物理):512字節/512字節

I/Osize(minimum/optimal):512bytes/512bytes

磁盤標識符:0x8c403069

設備啟動始點終點塊Id系統

/dev/sda1*16451200083Linux

第1分區沒有在柱面邊界結束。

/dev/sda2645105740960000083Linux

/dev/sda35105751318209715282Linux交換/Solaris

/dev/sda4513186080276176407+83Linux

屏幕上出現了警告信息:“第1分區沒有在柱面邊界結束”。于是在Web上檢索“磁盤的柱面”,即得到圖1.1這樣的說明圖。圖1.1典型的硬盤的說明圖結合之前輸出結果的第2行信息“磁頭255,扇區63,柱面60801”,此圖該如何解釋呢?那就是,這臺測試機器的硬盤“有255個磁頭數,每個磁道有63個扇區,一個磁盤可以分割出60801個磁道”。當然,物理上擁有255個磁頭的磁盤驅動器是不存在的。磁盤裝置的柱面數(cylinder)、磁頭(head)、扇區(sector)信息,三者統稱為CHS或“3D參數”,然而實際上,fdisk命令表示的3D參數信息和磁盤裝置的實際構造并沒有什么直接的關系。要弄清楚這是為什么,就需要理解硬盤的兩種不同類型(CHS方式和LBA方式)的存取方法(也經常被說成“尋址模式”)以及它們之間的差異。正如圖1.1中所示,要特別規定數據讀取和寫入的扇區(物理磁盤存取的最小單位)所處的位置,只需指定以下三個數值即可:柱面數(從最外緣開始計算第幾條磁道)、磁頭數(磁盤表面的讀/寫頭的個數)以及扇區數(磁道內等分弧段的個數)。使用時間超過10年的舊式磁盤中,磁盤裝置的這三個值(CHS值)指明了讀寫數據的位置,這就是CHS方式。但實際上,Linux設備驅動程序會計算CHS值,以進行磁盤裝置與數據之間的讀寫,因此用戶是不需要知道它們的具體數值的。在磁盤仍然使用CHS方式的時代,唯一一次需要用戶在操作時知道CHS值的,是在創建磁盤分區的時候。根據當時的MS-DOS方式,磁盤分區必須以扇區為單位進行操作。由于柱面是從磁盤的外緣開始按順序進行編號的,因而磁盤柱面的分割與編號大致如圖1.2所示。過去還曾有人異想天開,想在不同的物理磁盤(磁盤面)上采用不同的分割方式,但遺憾的是,這樣的分區方式是無法實現的。圖1.2典型的分區的分割方法雖然Linux沒有必要遵循MS-DOS方式,但在當時,通過將操作系統引入不同的分區,可以實現MS-DOS和Linux的多重引導或是Windows和Linux的多重引導。因此,和MS-DOS或Windows遵循共同的方式是有實際意義的。如此一來,和MS-DOS中的fdisk.exe一樣,Linux中的fdisk命令在做磁盤分區時,也得用柱面數來指定分區的開始位置和結束位置了。然而,這種有著悠久歷史的舊式硬盤早已被時代淘汰,淪為“計算機歷史博物館”中的陳列品了(這種說法可能有些夸張,但至少在筆者的家中是難覓其蹤了)。現在的硬盤普遍采用LBA(LogicBlockAddressing,邏輯塊尋址)方式進行數據存取。這種方式的機制極其簡單,即硬盤內所有的扇區均從0開始進行編號(扇區編號),通過扇區數來指定扇區的位置。扇區號與物理扇區位置之間的對應,是由內置的硬盤控制器來計算的。通常來說,扇區號越小,其對應的物理扇區就越位于磁盤的外側。與此相結合,分區的開始位置和結束位置同樣也是由扇區號來指定的。之前的fdisk命令的輸出中,是按照傳統的CHS方式,使用柱面號來表示各分區的起點和終點的,但實際上這并不是真實的信息。真正的分區信息,需要在fdisk命令之后加上選項-u來獲取。#fdisk-lu/dev/sda

磁盤/dev/sda:500.1GB,500107862016字節

磁頭255,扇區63,柱面60801,合計976773168扇區

Units=扇區數of1*512=512字節

扇區大小(邏輯/物理):512字節/512字節

I/Osize(minimum/optimal):512bytes/512bytes

磁盤標識符:0x8c403069

設備啟動始點終點塊Id系統

/dev/sda1*2048102604751200083Linux

第1分區沒有在柱面邊界結束。

/dev/sda2102604882022604740960000083Linux

/dev/sda3820226048824420351209715282Linux交換/Solaris

/dev/sda482442035297677316676176407+83Linux

如圖所示,各個分區的起點和終點是由扇區號來表示的。這些扇區號所劃分出的范圍就是實際的分區。圖中第一個分區是從2048號扇區開始的,由此可知0~2047扇區是保留扇區,它們不被用作分區。磁盤的開始部分為主引導扇區MBR,GRUBstage1.5就保存在MBR之后的空間中。這里的情形是,第0號扇區為主引導扇區,stage1.5則存儲在主引導扇區后直至第2047號扇區之間的空間中2。2第1分區的起始位置,以前的標準是第63扇區,最近變更為第2048扇區。而stage1.5并沒有那么大,因此在之前的起始位置保存stage1.5也沒有問題。繞了這么大一個圈,下面就開始解釋上文所提到的警告信息吧。由于現在的硬盤是以LBA方式存取的,因此分區的開始和結束位置皆通過扇區號來指定。雖然這樣做一點問題都沒有,但是fdisk命令為了支持舊式CHS方式的磁盤,仍然以CHS方式來表示磁盤信息。這時,對于LBA方式的磁盤,需要轉換成其對應的3D(磁頭數、扇區數、柱面數)參數。當分區的結束位置不能用3D參數中合適的柱面結束位置來對應表示時,就會出現諸如“第1分區沒有在柱面邊界結束”這樣的警告信息。總之,這種警告信息的出現,意味著“操作系統正以柱面為單位對磁盤進行分區,可能會導致一些問題”。不過對于在Linux下使用的磁盤,這種顧慮是多余的?,F在,在fdisk命令之后加上選項-u,便可以通過指定扇區號來指定分區。因此,今后還是養成在fdisk命令后附加選項-u的習慣吧3。3在RedHatEnterpriseLinux的高級開發版本Fedora所包含的fdisk命令中,默認的操作是以扇區為單位的。當需要進行舊式的以柱面為單位的操作時,需要指定選項-u=cylinders。1.2.2新舊分區表前一節介紹的表示分區開始和結束位置的信息,它們究竟會被寫入到磁盤的哪里呢?這是一個常常被問到的問題,答案毫無疑問是“分區表”。準確地說,分區表就是存放在第0號扇區MBR的446~509字節的部分。在MBR的0~445字節中,存放的是所謂的引導加載程序,即服務器啟動時,用于引導BIOS的加電自檢以及GRUBstage1的加載。由于一個扇區的大小是512字節,這里就會有510~511(從0字節開始,到511字節結束)兩個字節的剩余,于是按慣例這里的數值就記錄為0xAA55。若磁盤此處的值不為0xAA55,則判斷該磁盤的MBR已損壞。由于分區表的大小只有64個字節,因此大部分信息不能被寫入其中。在每個分區表中,只記錄著“用CHS方式描述的分區開始位置和結束位置”以及“用LBA方式(扇區號)描述的分區開始位置以及包含的扇區數”這類有代表性的信息(結束位置不用扇區號來記錄,而是通過開始位置與扇區數相加計算得到)。之所以要通過兩種方式記錄分區開始和結束位置的信息,是有其歷史原因的。LBA方式的磁盤實際上是不使用CHS方式記錄分區信息的。圖1.3是通過hexdump命令輸出的MBR第446字節開始往后66字節的內容(分區表加最后兩個字節)。通過設置詳細的選項,輸出了以十進制形式表示的LBA方式的分區信息。圖1.3分區表的轉儲輸出方框里的4行數據,分別是4個分區的信息。將它與之前帶選項-u的fdisk命令的輸出相比較,可以確定采用LBA方式記錄的扇區號的信息與之前的信息是一致的。CHS方式描述的數值在此不做詳細分析,但從分區的開始位置和結束位置出現了若干個相同的數值可以看出,這是段沒有意義的信息。由于CHS方式描述的信息實際上并不會被投入使用,因此問題不大,但還是應當注意避免混淆。順帶一提,最后兩個字節正是前面介紹的0xAA55,而把圖1.3的最后看成0x55AA的讀者,還請自行學習一下“小端”(littleendian)的知識。如此看來,使用LBA方式是最為簡單便捷的了。不過,近來這種方式也出現了它的局限性。那么是什么呢?如圖1.3所示,雖然采用十進制比較難懂,但能看出表示開始位置的扇區號和全體扇區數的數字總共是4個字節,因此可以表示的范圍僅為0x00000000~0xFFFFFFFF。換句話說,它無法支持扇區數超過0xFFFFFFFF的大容量磁盤。可以想象出這個容量是多大嗎?我們知道一個扇區是512個字節,用十六進制的計算器進行計算,答案應該是2TB。也許有人要問,如果沒有十六進制的計算器該怎么辦?即便沒有這樣的計算器,也可以在Linux上通過使用bc指令來計算,如下所示。ibase=16即指定“輸入值為十六進制”。另外這里提醒大家一下,0xFF表示的是十進制數256。#echo"ibase=16;FFFFFFFF*FF*2"|bc

2190433320450

因此,MBR中的分區表是有限度的,對于容量大于2TB的硬盤,是無法為之創建分區的。當使用外部存儲裝置LUN(邏輯磁盤)作為數據存儲區域時,則無需對LUN進行分區,只需將其格式化后掛載到文件系統中,或使用LVM(邏輯卷管理)方法將其作為邏輯卷進行管理等。這樣,容量大于2TB的LUN就也能夠使用了。不過,近來的服務器磁盤正逐步趨向大容量化,隨著容量大于2TB的本地磁盤的普及,找到容量大于2TB的磁盤的分區方法也指日可待。GPT(GUIDPartitionTable,GUID分區表)正是為了解決這個問題應運而生的?,F在,如果需要從使用GPT的硬盤中啟動操作系統,就需要服務器和操作系統都能支持UEFI。操作系統中,目前的RedHatEnterpriseLinux6(RHEL6)是能支持UEFI的。此外,雖然與分區表并不直接相關,但在最近的大容量磁盤中,有的已經以4KB作為一個扇區的大小了。接下來就對UEFI和GPT,以及4KB扇區的磁盤做一個詳細的介紹,內容或許略微復雜,請大家認真學習。UEFI和GPTUEFI是以后將要取代BIOS的一個方案。我們都知道,當服務器接上電源,系統BIOS就開始啟動。BIOS的啟動只允許使用1M的內存空間。因此系統BIOS的設置界面不是圖形界面,而是非常簡單的基于文本形式的界面。而且服務器上搭載的各種設備的設置不是通過BIOS的設置界面來操作的,而是需要通過Ctrl+A等按鍵來單獨操作設置界面。這些都是因為BIOS所能使用的內存空間有限。UEFI打破了BIOS的這些限制,在功能方面進行了多種擴展,于是支持UEFI的服務器在啟動時,就可以直接通過UEFI的設置界面來調用各種設備,有的服務器甚至已經有了圖形化的設置界面。此外,調用引導加載程序的方法也發生了改變。以前都需要像GRUBstage1、stage1.5、stage2這樣分階段啟動引導加載程序,而改進后,在以GPT方式創建的“EFI系統分區”中,引導加載程序存儲就可以直接被調用了。最后介紹一下GPT。過去的分區表在第0扇區MBR里,GPT則被寫入第1扇區至第33扇區中,成為一種新型的分區表(圖1.4)。表1.1中總結了這兩種分區方式的主要區別。圖1.4GPT的構造表1.1以前的分區和GPT的比較

以前的分區GPT分區表的位置MBRMBR之后(磁盤末尾亦有副本)最大磁盤容量2TB8ZB(無限)最大分區數15(使用SCSI磁盤時)1284分區卷標分區ID(表示用途的ID)GUID(表示用途的ID+唯一的ID)制作分區的工具fdiskparted4在設計GPT時,通過改變GPT頭的設置,也可以創建出超過128個分區。但一般情況下,128個仍是最大限度。為了降低分區表損壞的風險,GPT在硬盤的最后保存了一份同樣內容的分區表副本。GPT的頭部,則記錄了可以用作分區的扇區范圍。每個分區的信息都記錄在“分區表”中。一個分區表是128字節,一個扇區(512字節)可以記錄4個分區的信息。每個分區的開始扇區和結束扇區都分別用8個字節來記錄,因此即便是容量大于2TB的硬盤的扇區數,處理起來也是綽綽有余的。每個分區中都記錄著一個特定的GUID標簽。尤其是用來存儲引導裝載程序的分區,會附上“EFI系統分區”(ESP)的標簽。如果是RHEL6,/boot/efi下掛載的文件系統就是ESP類型的分區。這里保存了類似于GRUBstage2(grub.efi)的啟動過程。支持UEFI的服務器,會根據GUID定位ESP,啟動其中的grub.efi5,因此就不再需要GRUBstage1和stage1.5了。ESP采用的是VFAT格式。當創建一個GPT格式的分區時,應使用parted命令。表1.2描述了parted命令的主要內部命令,它們的具體使用方法可以在網上查到,此處不再贅述。表1.2parted命令的主要內部命令

命令說明check對文件系統進行檢查cp復制分區help顯示對相關命令的說明?!癶elp命令名稱>”可以顯示每一條命令的詳細信息命令名稱>mkfs創建文件系統mklabel指定表示分區表類型的磁盤標簽。在使用GPT時即指定“gpt”mkpart創建分區mkpartfs進行分區和文件系統的創建move移動分區print顯示當前的分區表或磁盤標簽的狀態quit結束parted命令resize改變分區的大firm刪除分區select指定要處理的設備(例如:/dev/sda)set設置包括引導標志在內的各種標志由于RHEL6也支持GPT,因此在容量大于2TB的硬盤上進行安裝時,會自動采用GPT方式進行分區。TechnicalNotes[2]使用RHEL6Rescue模式的備份指南(非LVM環境/NFS環境/uEFI模式版)\h/jp/linux/tech/doc/attachments/003bc366_rhel6-rescuee383a2e383bce38389e38292e4bdbfe794a8e38197e_12.pdf64KB扇區的磁盤以前的傳統硬盤,一個扇區固定為512字節。而對于大容量硬盤,則通過增加扇區的大小,來減小訪問扇區產生的消耗。另外,硬盤內部記錄了每個扇區錯誤校驗所需要的信息。通過增加扇區的大小,可以降低這些附加信息所占的百分比,使記錄數據的空間得到更加有效的利用。但是,由于訪問硬盤的服務器硬件或操作系統(設備驅動)都是按照之前512字節的扇區大小設計的,因此不能一味單純地增加硬盤扇區的大小。為了實現兩者的兼容,就產生了通過硬盤中的控制器來從邏輯上模擬512字節扇區的運作方式。如圖1.5所示,從服務器的角度來看,扇區的大小仍然是512字節,但實際的數據讀取和寫入是在4KB扇區上進行的。最近有不少新面世的硬盤均采用了這種處理方式。圖1.54KB扇區的磁盤結構乍一看,這種轉換方式會產生一定的開銷。在讀取4KB扇區中的邏輯扇區數據時,即便僅僅512個字節的讀取和寫入,也會需要對整個4KB扇區進行操作。但是實際上,若是大數據的讀取和寫入是從4KB扇區的起始位置開始的,則基本上并不會產生額外的開銷。要有效地實現這樣的效果,不僅需要將分區的開始位置與4KB扇區的起始邊界對齊,還需要將文件系統的塊大小調整為4KB(4096字節)(關于文件系統的塊大小,將在下一節中進行詳細介紹)。在筆者之前介紹的測試機的例子中,第一個分區/dev/sda1是從第2048扇區開始的。如果將其轉換成圖1.5的邏輯區段數,就恰好和第256個4KB扇區的起始邊界對齊。因為分區中包含的扇區數也是8的倍數,因此分區的結束位置也就正好是4KB扇區的結束邊界。/dev/sda2和/dev/sda3也是如此,這些分區是在RHEL6的安裝界面中設置的??梢姲惭b程序很嚴謹地考慮到了這些問題。當然,這些都是僅在使用4KB扇區的硬盤時才需要注意的事項,但將來一定會有越來越多的硬盤采用4KB扇區。因此,在設置分區的開始位置以及大小時,將邏輯扇區數設置為8的倍數是比較好的7,這種方法被稱為“分區對齊”。在用parted命令創建GPT分區時,默認情況下會指定分區的開始位置、結束位置和容量(例如MB)。此時,parted命令會自動對齊所創建的分區。如果不放心的話,可以通過“units”命令來指定扇區,對分區的開始和結束位置進行確認。5恢復系統備份后,有時候需要通過UEFI的設置界面重新設置啟動對象的啟動加載文件。點擊[2]的鏈接可以看到使用IBMSystemx時的操作順序。6此處為日文資料?!g者注74KB扇區的磁盤中有一個被稱為“對齊偏移”的功能,在該功能有效的情況下,需要將分區的起始位置設為“8的倍數+7”。由于這是Linux中不需要的功能,因此在能夠通過硬盤的跳線開關等進行更改的情況下,建議禁用該功能。詳情請參考[3]。1.2.3文件系統和I/O子系統文件系統的塊大小我們先來重新思考一下文件系統的數據訪問。說到文件系統,大家可能會想到ext3、ext4等,但在本節,筆者打算從更為宏觀的角度來介紹文件系統。首先,Linux中,有將各種不同的文件系統統一起來的VFS(VirtualFileSystem,虛擬文件系統)層,還有通過設備驅動將數據讀取或寫入物理磁盤的塊層,它們一起組成了圖1.6中所示的I/O子系統。Linux的文件系統只是VFS層中的一部分。圖1.6I/O子系統的結構TechnicalNotes[3]LinuxKernelWatch:超過2TB!ATA磁盤的4KB扇區問題是什么?\hhttp://www.atmarkit.co.jp/flinux/rensai/watch2010/watch03a.html8前文提到的“文件系統的塊大小”,是塊層中的設備驅動程序將數據讀取或寫入物理磁盤的最小單位。從物理磁盤的結構上看,是以512字節的扇區單位來讀取和寫入數據的,但很多時候采用較大的單位來讀取和寫入數據可以更有效地進行數據交換,而指定這種單位的就是塊大小。在Linux中,文件系統的塊大小有1024字節、2048字節和4096字節這幾種選項。默認的塊大小被記錄在配置文件/etc/mke2fs.conf中,也可以通過mke2fs命令的-b參數來明確指定塊大小。若要對已經創建好的文件系統所設置的塊大小進行確認,則應使用tune2fs命令9。我們在上一節中提到,在4KB扇區磁盤的情況下,應使分區的開始位置與4KB扇區的起始邊界相對齊,并將文件系統的塊大小也設置為4KB(4096字節)。圖1.7給出了這樣做的原因。圖1.7中,上圖表示了滿足這種條件的情況,設備驅動程序對物理磁盤的訪問實際上只對應了一個4KB扇區,沒有產生無效的數據讀寫。圖1.74KB扇區和塊大小的映射下圖則表示了分區的開始位置與4KB扇區的起始邊界沒有對齊的情況。打個比方,假使設備驅動程序寫入了一個塊。由于物理磁盤不能只對4KB扇區的一部分進行重寫,因此需要先讀取出兩個扇區的數據,按要求對其中的部分數據進行重寫,然后再將處理后的數據重新寫入這兩個扇區。這顯然會造成額外的開銷。鏈接[4]里比較了分區的開始位置與4KB扇區的起始邊界對齊和不對齊的情況下分別對磁盤訪問性能造成的影響。據分析,分區開始位置發生偏離時,磁盤的數據寫入性能會出現顯著的下降。順便說一下,類似的情形在常見的512字節扇區的磁盤中也會發生,當分區的大小不為塊大小的整數倍時,最終會有一個塊超出分區。這種情況下,最終的這個塊就不會在文件系統中使用。圖1.8即為塊大小為2048字節的一個例子,這時分區剩下的最后兩個扇區就不會被使用。TechnicalNotes[4]4KB扇區磁盤上的Linux:實際建議\h/developerworks/cn/linux/l-4kb-sector-disks/圖1.8最后的塊超出分區的情況還是題外話,在fdisk命令的輸出中,有時塊數的值后面可以看到附加的+記號。比如“1.2.1磁盤的3D參數”一例中,/dev/sda4的塊數(分區中包含的扇區數)后就有+記號。當塊數為奇數值時,便會附加上這個符號。這是有其歷史原因的。過去的Linux文件系統的塊大小規定為1024字節,因此當分區中的扇區數為奇數值時,就會剩下最后的扇區不被使用。+記號就是為了表示“該分區最后一個扇區不會被使用”而特意附上的,但現在這也許是多余的了。I/O子系統的概貌我們來回顧一下圖1.6中介紹的I/O子系統的整體結構,分別從寫入數據和讀取數據兩種情況進行考慮。首先來看看寫入數據的情形。當數據被寫入文件系統的文件中時,其內容會被暫時錄入到磁盤高速緩存中(①)。這時發出寫入數據命令的應用程序(用戶進程)會將此步驟視為數據已錄入成功,從而進行下一步操作。然而此時就會有些數據只是被寫入到了磁盤高速緩存中,而沒有被寫入到物理磁盤中去。這種數據稱為“臟數據”。當磁盤高速緩存上的臟數據積累到一定程度時,文件系統便會向I/O調度發出請求,將這些臟數據寫入物理磁盤(②)。這個寫入請求將被添加到I/O調度內部的“請求隊列”中。最后,I/O調度器會響應請求隊列中的請求,利用設備驅動程序將數據寫入物理磁盤中(③④)。大家可能聽說過多種類型的I/O調度器,例如cfq、deadline等。它們之間的差別在于使用的算法不同,即按何種順序處理請求隊列中的請求才最高效。關于I/O調度器,之后會做更加詳細的說明。此外,表1.3中列出的內核參數,能夠用于調整臟數據寫入的頻率。不過除非有特別的原因,否則是不需要更改默認值的。表1.3與數據的寫出頻率相關的內核參數

內核參數說明vm.dirty_background_radio

vm.dirty_radio試圖將緩存中臟數據的百分比保持在“dirty_background_radio(%)”之下。尤其是當臟數據的百分比超過“dirty_radio(%)”時,將立即增加寫出頻率vm.dirty_background_bytes

vm.dirty_bytes以字節為單位操作與上面相同的指定。優先于上面的指定,設置為0時則保持上面的指定vm.dirty_writeback_centisecs

vm.dirty_expire_centisecs每“dirty_writeback_centisecs(單位為1/100秒)”檢查磁盤緩存,每超過1“dirty_expire_centisecs(單位為1/100秒)”,就持續寫入新的數據至于數據的讀取,則是按以下流程進行的。假設應用程序(用戶進程)要從文件系統的文件中讀出數據。如果目標數據已經存在于磁盤的高速緩存中,文件系統便將這些數據返回給進程,從而完成處理(①)。反之,如果目標數據不在磁盤的高速緩存中,那么I/O調度的進程會暫停執行并進入等待狀態,之后該文件系統會向I/O調度器發出讀取數據的請求(②)。最后,I/O調度器響應請求,通過設備驅動程序把數據從物理磁盤讀入磁盤高速緩存中(③④),解除I/O調度進程的等待狀態,再接著處理磁盤高速緩存中的數據(①)。磁盤高速緩存和文件系統屬于VFS層,I/O調度和設備驅動屬于塊層,二者相互配合來完成文件中數據的讀取和寫入。之所以將它們分為兩個不同的層,是基于Linux內核中“模塊化結構”的概念。這樣就可以將不同類型的文件系統和不同類型的物理磁盤隨意組合起來使用。再舉個極端的例子,即便是沒有物理磁盤的文件系統也能實現數據的處理。例如,ramfs是利用內存來提供RAM磁盤功能的文件系統,它就完全沒有圖1.6的②(數據傳輸請求)這個步驟。那么寫入的數據究竟到哪里去了呢?實際上它們都保留在磁盤緩存中。若是一般的文件系統,臟數據一旦被寫入物理磁盤,就不再是臟數據,因此需要從磁盤高速緩存中刪除。但ramfs則不同,磁盤高速緩存中的數據永遠是臟數據,永遠不會被刪除。ramfs被描述成“基于內存的RAM磁盤”,其實準確來說,應該是“基于磁盤高速緩存的RAM磁盤”。在Linux中,還有一個和ramfs具備相同功能的tmpfs。tmpfs同樣是將數據保存在磁盤高速緩存中,但有別于ramfs的是,它的內容還可以成為“換出”的對象。換句話說,當物理內存不足時,存儲在tmpfs的文件會被寫入物理磁盤的交換空間中,這樣文件使用的內存就可以被釋放。關于tmpfs這種允許換出的機制,在本章1.4.1節中也會所介紹。看到這里,可能有人會產生這樣的疑問:“ramfs和tmpfs,到底選用哪個好呢?”要是只作一般用途,例如應用程序中所使用的數據的臨時存儲,建議還是采用tmpfs。這是因為,tmpfs有設置內存使用上限的功能,而ramfs則沒有這樣的功能。如果無限制地往ramfs上保存文件,就會持續消耗內存,而且內存部分也不能被換出,最終就會因為內存不足而啟動OOMKiller。順帶一提,ramfs的原作者正是Linus先生,在源碼ramfs/inode.c中還能看到Linus先生留下的注釋。*NOTE!Thisfilesystemisprobablymostuseful*notasarealfilesystem,butasanexampleof*howvirtualfilesystemscanbewritten.以上注釋都表明,ramfs只是作為一個最簡單的文件系統的示例而創建出來的,它并不適合實際使用。不過,在Linux內核中,有一個地方是使用ramfs的。在Linux的引導過程中,內核啟動后,初始RAM磁盤內容10會被裝載到RAM磁盤空間中。這里的RAM磁盤空間就需要用到ramfs。理解I/O調度器我們繼續回顧圖1.6,看看I/O調度器是如何處理文件系統的數據傳輸請求的。磁盤高速緩存和物理磁盤之間進行數據傳輸時,磁盤高速緩存中數據的存儲方式和物理磁盤中數據的存儲方式是不一致的。首先,在磁盤高速緩存上,一個文件中連續的數據基本上會被寫入到連續的存儲空間中。然而,同樣的數據在物理磁盤上就不一定會被放置在連續的存儲空間中了,這種情況被稱為“磁盤碎片”。如圖1.9所示,磁盤高速緩存中連續的數據被分成了兩個部分,分別被寫入了物理磁盤上兩個連續的扇區內。在本圖的例子中,文件的開始和結束部分的內容被設置在了物理磁盤上相鄰的位置。此外,Linux的內存以4KB的頁為管理單位,磁盤高速緩存中1個頁能寫入8個扇區的數據。圖1.9磁盤高速緩存和物理磁盤的對應示例正如圖例所示,文件系統的作用就是決定如何將文件的數據分配在磁盤上,而圖1.9中的對應關系正是記錄在元數據區中的。因此,圖1.6中文件系統向I/O調度器發出數據傳輸請求時,文件系統就會對物理磁盤上的連續數據進行分割處理。如此一來,I/O調度器就能夠有效地讀取物理磁盤上的連續數據。說得夸張點,即便在考慮性能問題時,對I/O調度器的理解也是非常重要的,因此這里會對I/O調度器做盡可能全面的介紹。首先,文件系統會將數據分配到物理磁盤上的連續空間(連續扇區)中,在內核中整合創建出一個bio對象,再把這個bio對象傳遞給I/O調度器。從圖1.9中可以看出,有兩個bio對象要傳遞給I/O調度器。I/O調度器接收到來自文件系統的bio對象,會將多個bio對象合并為一個請求對象,再將它加入到請求隊列中。(圖1.10)圖1.10I/O調度器的結構請求對象的合并方法因I/O調度器而異,一般來說,如果bio對象指定的連續扇區同時也是請求隊列中已有的請求對象所指定的連續扇區,那么就會將這兩個對象合并。如果沒有一致的請求對象,則創建一個只包含該bio對象的新的請求對象。最后,物理磁盤空間上的數據才會在請求對象所指定的區域進行讀取或寫入。連續扇區上數據的讀取和寫入就是這樣一并進行的。此外,這些請求對象的實際處理順序也是因I/O調度器而異的。例如cfq(completefairqueuing)調度器,其進程的bio對象會被加入到不同的子隊列中,以便盡量公平處理每個進程的請求。從圖1.10中可以了解子隊列的機制。I/O調度器接收的bio對象,首先會被作為請求對象加入到子隊列中。子隊列中的請求對象增加到一定程度后,便會按照實際傳輸處理的順序從子隊列轉移到調度隊列中,進行實際的數據處理?,F在大家應該都明白I/O調度器的機制了吧。接下來對具體的I/O調度器之間的區別進行介紹。Linux中主要使用的是以下四種I/O調度器,它們的特征分別如下:noop(nooptimization)調度器:只有一個子隊列,按照bio對象被接收時的順序處理請求。cfq(completefairqueuing)調度器:bio對象被接收后,獲取發送該bio對象的進程ID,根據散列函數,將進程ID映射到6411個子隊列的某個隊列中,再處理各個子隊列中一定量的請求。這樣,每個進程的I/O請求都會被公平執行。deadline調度器:分別用不同的隊列來維護讀請求和寫請求。將接收的bio對象根據其對應的扇區位置插入到請求隊列中最合適的位置(位置的排列以盡量減少磁盤頭的移動為原則)。但是,當新的bio對象被不斷插入到請求隊列的前方時,隊列后面的請求恐怕就會長時間得不到處理,因此需要優先處理一定時間內(讀請求為0.5s,寫請求為5s)沒有得到處理的請求。anticipatory調度器:在deadline調度器的基礎上,追加了“預測”下一個bio對象的功能。例如,當收到進程A發出的讀請求后緊接著又收到了來自進程B的請求時,就可以預測進程A可能很快會發出另一個讀請求,這種情況下就要在調度進程B的請求之前延遲一段時間(0.7秒左右),以等待來自進程A的下一個讀請求。這些用來處理請求的不同I/O調度方法也被稱為“電梯算法”。因為要在最大限度減少磁盤頭移動的情況下,讀取和寫入盡可能多的數據,這就好比大廈里的電梯,通過最少的運動承載盡可能多的人。最早在Linux中使用的電梯算法是由Linus先生設計的,因此過去也曾被稱為“Linus電梯”。RHEL6中默認的I/O調度器是cfq。如果需要對單個硬盤所采用的I/O調度器進行修改,可以通過echo命令在sys文件系統的特殊文件/sys/block/<磁盤名>/queue/scheduler中寫入I/O調度器的名稱(noop、anticipatory、deadline或cfq)。下面就是一個變更I/O調度器的例子,將/dev/sda當前使用的cfq調度器更改為了deadline調度器。#cat/sys/block/sda/queue/scheduler

noopanticipatorydeadline[cfq]

#echo"deadline">/sys/block/sda/queue/scheduler

#cat/sys/block/sda/queue/scheduler

noopanticipatory[deadline]cfq

如果只是確認當前的I/O調度器,也可以使用lsblk命令。如下SCHED部分表示的就是當前所采用的I/O調度器。#lsblk-t

NAMEALIGNMENTMIN-IOOPT-IOPHY-SECLOG-SECROTASCHEDRQ-SIZE

sda051205125121cfq128

├─sda1051205125121cfq128

├─sda2051205125121cfq128

├─sda3051205125121cfq128

└─sda4051205125121cfq128

如果需要修改系統默認的I/O調度器,則在GRUB的配置文件/boot/grub/grub.conf的內核啟動項中設置“elevator=deadline”。最后需要注意的一點是,對于被dev/sda等指定的設備,I/O調度器可以將其想象成圖1.1中扇區排列在物理磁盤的磁道上那樣的構造,實現訪問的最優化。但是在使用外部磁盤裝置的情況下,RAID陣列的邏輯驅動器(LUN)會被識別為/dev/sda等設備。這種時候,存儲裝置的控制器也能實現磁盤訪問的最優化,就不需要Linux的I/O調度器去進行過于復雜的優化工作了。僅就一般而言,對于在數據庫服務器或服務器虛擬化環境這樣使用高性能外部存儲設備的復雜環境中的數據訪問,有時侯deadline調度器要比cfq調度器效率更高。順便說一下,雖然cfq調度器的解釋是“對每個進程實行公平的I/O處理”,但是為了公平的處理,有時候也需要刻意營造一些不公平。大家或許并不熟悉,其實通過ionice命令可以對cfq調度器中的每一個進程設置優先級。優先級有三類,分別是Realtime、Besteffort和Idle。和系統上的其他進程相比較,Realtime級別的進程的I/O是最為優先被處理的。而Idle級別進程的I/O正相反,只有在系統上所有進程的I/O都處理完了的情況下才處理它。換句話說,除了這類進程之外沒有別的進程的I/O需要被處理,即系統處于空閑狀態時,才會對它進行處理。Besteffort則是默認的優先級,基于cfq調度器的正常邏輯,公平地進行I/O處理。對于優先級為Realtime和Besteffort的進程,還能在同一級別中依次指定數值為0~7的優先級參數,數值越小,優先級越高。因此,對于那些由于特殊原因不允許延遲的進程,就可以將其優先級設置為Realtime級別。例如,RHEL6標準的HA集群系統(HighAvailabilityAdd-On)中,有一種被稱為“仲裁磁盤”(quorumdisk)的機制,它通過將數據定期寫入共享磁盤來確認服務器是否運行正常。若寫入出現延遲,有時會誤報服務器發生故障,因此就可以考慮將該進程(qdisk)的優先級設置為Realtime級別。但是也要注意,如果某進程有大量的I/O處理,若還將其優先級設置為Realtime級別的話,其他進程的處理就會完全停滯,從而引發其他問題。關于ionice命令的使用,可以參考手冊頁。要想確認ionice的運行效果,則推薦試試下面這個命令。#ionice-c1ddif=/dev/zeroof=/tmp/tmp0bs=1Mcount=500oflag=direct&ionice-c3ddif=/dev/zeroof=/tmp/tmp1bs=1Mcount=500oflag=direct&wait

[1]18764

[2]18765

500+0recordsin

500+0recordsout

524288000bytes(524MB)copied,4.59358s,114MB/s

[1]-結束ionice-c1ddif=/dev/zeroof=/tmp/tmp0bs=1Mcount=500oflag=direct

500+0recordsin

500+0recordsout

524288000bytes(524MB)copied,9.3951s,55.8MB/s

[2]+結束ionice-c3ddif=/dev/zeroof=/tmp/tmp1bs=1Mcount=500oflag=direct

這個例子中有兩個同時執行的dd命令,這里dd命令用于導出500M大小的文件,它們的優先級分別被設置為Realtime和Idle。Idle優先級的I/O處理完全被推遲,在Realtime優先級的dd命令結束后,Idle優先級的dd命令才開始執行。這樣一來,Idle優先級的執行時間正好是Realtime優先級的兩倍(圖1.11)。圖1.11Realtime級別和Idle級別dd命令中的指定選項oflag=direct,表示直接寫入物理磁盤而不使用磁盤高速緩存,常常被用來測量物理磁盤的性能。至于最后wait命令的含義,我們將在下一節中進行說明。8此處為日文資料?!g者注9RHEL6的mke2fs命令,以及tune2fs命令對應于ext4文件系統。mkfs.ext4命令,等同于“mke2fs-text4”。10Linux初始RAM磁盤initrd是系統引導過程中掛載的一個臨時根文件系統,它與RAM磁盤空間是兩個概念。——譯者注11默認為26=64。——譯者注1.3控制進程就等于控制Linux眾所周知,Linux上的工作都是通過進程來執行的。若把系統的各種行為擬人化,進程的一生便是時而穩定,時而“死去”又“活來”,可謂十分曲折。有的進程甚至每天都被嚴密地監視著。在本節,我們將再次探討進程管理機制的基本知識,看看這種機制是如何支撐和左右這些進程的命運的。尤其在理解了進程的fork之后,腳本的創建技術將得到質的提高。使用fork來編寫腳本的方法,在本書3.4.3節中亦有介紹。1.3.1fork和exec分別是進程的分身和變身登錄系統控制臺時,需在“login:”提示符處輸入用戶名進行登錄。這里“login:”提示符表示的是接收用戶名的輸入,執行該步驟的是mingetty進程。登錄成功后,命令提示符下顯示bash啟動。確切地說,是在bash進程啟動時,即顯示相應的命令提示符。然后,在命令提示符下運行ls命令啟動ls進程,可以顯示當前目錄下的文件名。大家可能覺得這些都是小兒科,實際上,這一連串進程動作的發生隱藏了一個至關重要的玄機。我們都知道,在Linux上創建進程有fork和exec兩種方法,大家能準確地說出這兩者的區別嗎?干脆來做一個小小的實驗吧。首先,在運行級別3下啟動一個Linux服務器,之后SSH遠程登錄,查看mingetty進程的執行情況。#ps-ef|grep"mingett[y]"

root16921019:21tty100:00:00/sbin/mingetty/dev/tty1

root16941019:21tty200:00:00/sbin/mingetty/dev/tty2

root16961019:21tty300:00:00/sbin/mingetty/dev/tty3

root17001019:21tty400:00:00/sbin/mingetty/dev/tty4

root17021019:21tty500:00:00/sbin/mingetty/dev/tty5

root17041019:21tty600:00:00/sbin/mingetty/dev/tty6

grep命令的參數中,通過將它最后一個字母y寫成[y]的方法,可以確保grep進程名的結果中不包含有與進程名不符的信息12。這里找出了6個mingetty進程。這六個虛擬控制臺,可以通過Ctrl+Alt+F1~F6分別切換到它們對應的“login:”提示符下。12思考一下,為什么grep命令中指定的檢索字符串就可以順利地使用正則表達式呢?在控制臺中按下Ctrl+Alt+F1,出現“login:提示符”,輸入用戶名,然后輸入后回車,出現輸入密碼的提示。這里請先不要輸入密碼。這里再次在SSH遠程登錄的終端,通過ps命令查看mingetty進程。#ps-ef|grep"mingett[y]"

root16941019:21tty200:00:00/sbin/mingetty/dev/tty2

root16961019:21tty300:00:00/sbin/mingetty/dev/tty3

root17001019:21tty400:00:00/sbin/mingetty/dev/tty4

root17021019:21tty500:00:00/sbin/mingetty/dev/tty5

root17041019:21tty600:00:00/sbin/mingetty/dev/tty6

mingetty進程的數量減少了一個,ID為1692的mingetty進程似乎消失了。難道這個進程接收一個用戶名就會消失嗎?并非如此。這一次,用ps命令檢索進程ID。#ps-ef|grep"169[2]"

root16921019:21tty100:00:00/bin/login--

顯示的是login進程。知道為什么會這樣嗎?事實上,mingetty進程在exec()系統調用的作用下,已經轉變為login進程了(圖1.12左圖)。圖1.12exec和fork中進程的變化Linux的進程,除了進程主程序代碼外還有其他各種各樣的信息,比如用于識別進程的進程ID就是其中之一。除此之外,還有用于和其他進程或文件交換數據的管道、文件描述符等。exec的作用是舍棄進程原本攜帶的信息,在進程執行時用新的程序代碼替代調用進程的內容。mingetty進程的工作則是接收登錄用戶名,但之后對密碼的驗證處理工作則移交給login進程繼續完成。圖1.13是mingetty進程中運行exec的部分源代碼。每行開頭的編號表示源代碼中實際的行號。454行的函數execl,通過exec()系統調用切換到變量loginprog所指定的程序中去(這個例子中是/bin/login)13。這之后執行的便是/bin/login進程,因此通常455行之后的代碼便不會再運行了。只有在exec()系統調用出現失敗的情況下,才會運行455行之后的代碼。13調用exec()系統調用的C語言函數有execl、execlp、execle、execv、execvp等。execl和execv,由于命令的參數的傳遞方法的不同,即便最后都附加p,在PATH環境變量中進行命令檢索時也仍有區別。mingetty.c454execl(loginprog,loginprog,autologin?"-f":"--",logname,NULL);

455error("%s:can'texec%s:%s",tty,loginprog,strerror(errno));

456sleep(5);

457exit(EXIT_FAILURE);

圖1.13mingetty中運行exec的部分源代碼之后,exec開始執行login進程,在接收密碼輸入完成用戶認證后,便啟動用戶的bash進程。之前出于實驗需要中斷了密碼的輸入,實際上,只有輸入密碼才算完成了登錄操作。在輸完密碼的情況下,我們再次檢索同一個進程的ID。#ps-ef|grep"169[2]"

root16921019:21?00:00:00login--root

roo29tty100:00:00-bash

login進程仍然保留了與之前相同的進程ID(1692)。另外,此次檢索結果中還包含了bash進程。bash進程本身的進程ID(1818)與檢索的進程ID并不匹配,但是它的父進程的進程ID(1692)與grep檢索中的進程ID互相匹配了(ps–ef命令的輸出中依次包含用戶ID、進程ID以及父進程的進程ID)。這說明bash進程是作為login進程的子進程開始啟動的。通常我們說“fork一個進程”,指的是通過父進程創建一個子進程。但現在的情況是,bash進程并不是從login進程中直接fork出來的。從圖1.12的右圖可以看出,fork生成的子進程是一個與正在運行的進程完全相同的副本。login進程通過fork生成一個自身的副本后,又在子進程中通過exec啟動bash,這一技術簡稱為“fork-exec”。圖1.14描述了它的整個流程。圖1.14從虛擬控制臺登錄時伴隨的進程的變化如果諸位仍覺得對fork-exec一頭霧水,那么就從login的源代碼中看看fork-exec實際的執行過程吧。Linux和開源最偉大的地方就是無論什么都能親自探尋。關于如何學習源代碼,在本書的第4章中也有所介紹,這里只是暫時先在圖1.15中給出答案。以//標記的注釋,是筆者自己補充的。login.c1183child_pid=fork();

//子進程的進程ID賦值給父進程的child_pid

//子進程的child_pid中代入0

1184if(child_pid<0){

//fork失敗時的錯誤處理

//(部分源代碼省略)

1189exit(0);

1190}

1191

1192if(child_pid){

//父進程從這里開始執行

//這個例子中,等子進程結束后,自己也就結束了

//(部分源代碼省略)

1201/*waitaslongasanychildisthere*/

1202while(wait(NULL)==-1&&errno==EINTR)

1203;

//(部分源代碼省略)

1206exit(0);

1207}

1208

//子進程從這里開始執行

1209/*child*/

1210

//(部分源代碼省略)

//子進程通過exec變換為bash

1280execvp(childArgv[0],childArgv+1);

圖1.15login中進行fork-exec的源代碼首先,在1183行代碼中直接執行了fork。這里的進程一分為二,且都是從1184行開始繼續執行的。當然這樣并沒有意義,因為這兩個進程是完全相同的,所以之后子進程需要通過exec變換為bash進程。這里用到一個小技巧,即把fork的返回值賦給變量child_pid。fork執行失敗時,父進程返回負值。fork執行成功時,父進程以及通過fork創建的子進程返回的值卻不相同。父進程的child_pid值為子進程的進程ID,而子進程的child_pid值為0。因此,父進程在1192行的if語句上條件成立,并執行if語句中的代碼。這個例子中,父進程會一直等待子進程結束,直到子進程退出時,父進程才結束。子進程跳過1192行的if語句,從1209行處開始執行。這個例子中顯示的是在1280行調用exec,進程變換為bash繼續執行。fork也可以采用Perl等腳本語言來實現。如果你不擅長C語言編程,那就請務必牢記這里所介紹的fork的處理技術。最后來看看進程結束的過程吧。首先,父進程login通過執行圖1.15中1202行的wait函數,一直休眠直到子進程結束。如下例所示,通過ps命令確認進程的狀態,可看到附加的表示休眠狀態的S符號。#psaux|grep"logi[n]"

root16920.00.2770042532?Ss19:210:00login--root

這時在已登錄的虛擬控制臺上顯示的bash命令提示符處,輸入exit進行用戶注銷。此時,通過exit()系統調用,bash進程被終止,同時發送CHLD信號給父進程login。接收到CHLD信號的父進程login會退出wait函數,同時結束進程。wait是一個函數,它讓父進程在接收到子進程CHLD信號之前一直保持休眠狀態。而另一方面,子進程向父進程發送CHLD信號,直到父進程接收為止,子進程都處于“僵尸進程”的狀態。如果父進程設置為忽略CHLD信號,子進程就會一直保持僵尸狀態。在通過psaux來查看進程狀態時,僵尸狀態會被標記為Z,信息末尾會顯示<defunct>。關于CHLD信號等這一類的進程信號,請參見相關書籍。圖1.16表示的是bash進程在被exit終止后的一系列流程。從圖中可以看到,login進程在結束之際向某處發送了CHLD信號,那么誰會接收到這個信號呢?圖1.16從虛擬控制臺退出時伴隨的進程的變化從之前ps命令的輸出中可以看到,login進程的父進程是進程ID為1的init進程。因此,init進程將會接收CHLD信號,這時login進程就可以從僵尸狀態中解放出來。另一方面,接收到CHLD信號后,init進程會檢測到login進程(或是原始的mingetty進程)已經終止了,于是會再度啟動新的mingetty進程,這樣便可以在虛擬控制臺上再次登錄了。啟動mingetty進程需要用到之前介紹的fork-exec,因此mingetty進程相當于init進程的子進程。此外,一直到RHEL5,init進程都是根據配置文件/etc/inittab來重新啟動mingetty進程的。但如今發展到RHEL6,init進程的啟動機制已替換為被稱作“Upstart”的新機制,啟動時采用的是/etc/init下的配置文件。關于Upstart,5.2節會有詳細的介紹。最后介紹一下與fork-exec機制相關的進程監控的問題。用fork-exec創建新的進程時,最初fork的瞬間,子進程的進程名和父進程的進程名是相同的。之后才通過exec變更為新的過程名。因此,在進行進程監視,對特定進程名的進程數量進行檢查的時候,在fork-exec執行的那一瞬間,需注意進程數量可能大量增加。如果設定為“不允許存在同樣命名的進程”的話,fork-exe和監視間隔的時間會出現微妙的一致,從而誤檢測到異常的進程數。這種現象并不少見,因此為了避免判斷時帶來麻煩,請務必注意到fork-exec的存在。1.3.2作業控制中的各項任務處理前文介紹了進程啟動的基本原理。下面再來介紹一些實用的技術吧。首先,在shell命令提示符下運行命令,通常在命令執行完畢后,命令提示符會再度出現,這與login進程啟動bash進程的流程(圖1.14)是一樣的。bash進程首先響應命令,通過fork-exec創建了一個新的進程,之后便會一直處于休眠狀態直到子進程結束。接著bash進程接收到CHLD信號,確認子進程已終止,再次顯示命令提示符,提示接收下一個命令。在命令的末尾加上“&”符號,可以讓命令在后臺運行。這種情況和上文一樣,bash進程會通過fork-exec創建新的進程,不同的是,此時bash進程不再需要等待子進程結束,便可以直接顯示命令提示符,提示接收下一條命令。在執行比較花時間的命令時,讓多條命令并行執行不失為一種便利的方法。但這里存在一個應用的問題。怎樣才能讓多條命令并行執行,并且等待所有命令都執行結束呢?舉個例子,可以考慮通過shell腳本實現SSH在10臺服務器上執行遠程命令,并等待所有服務器上的命令執行完畢。圖1.17是筆者實際使用過的shell腳本的例子,其目的是在三臺服務器(node01~node03)上啟動群集服務(clstart)[5]。為方便起見,將該腳本命名為clstart_all.sh。TechnicalNotes[5]HighAvailabilityAdd-On設計和運用入門“集群的啟動順序”\h/enakai/rhel6-rhcs-guidepreview-87581121414此處為日文資料?!g者注例1在每一臺服務器上執行#!/bin/sh

sshnode01/usr/local/bin/clstart

sshnode02/usr/local/bin/clstart

sshnode03/usr/local/bin/clstart

例2命令執行完之前結束#!/bin/sh

sshnode01/usr/local/bin/clstart&

sshnode02/usr/local/bin/clstart&

sshnode03/usr/local/bin/clstart&

例3命令執行完之后退出#!/bin/sh

sshnode01/usr/local/bin/clstart&

sshnode02/usr/local/bin/clstart&

sshnode03/usr/local/bin/clstart&

wait

圖1.17在多個服務器上并行執行命令的Shell腳本(clstart_all.sh)如例1中所示,node01運行clstart結束后,node02接著執行,即每一臺服務器按順序依次執行。這顯然是很浪費時間的。因此,在例2中,筆者嘗試在后臺執行每個命令。這種情況下,三臺服務器能同時正確執行clstart命令。但是,在每一臺服務器上的clstart命令運行結束前,腳本的運行就已經結束了。這里就出現了一個問題,即沒法在所有服務器的群集服務啟動完成后,再接收下一條輸

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論