導航:首頁 > 操作系統 > linux缺頁異常

linux缺頁異常

發布時間:2023-03-06 21:46:20

linux中使用了什麼內存管理方法,為什麼

「事實勝於雄辯」,我們用一個小例子(原形取自《User-Level Memory Management》)來展示上面所講的各種內存區的差別與位置。

進程的地址空間對應的描述結構是「內存描述符結構」,它表示進程的全部地址空間,——包含了和進程地址空間有關的全部信息,其中當然包含進程的內存區域。

進程內存的分配與回收

創建進程fork()、程序載入execve()、映射文件mmap()、動態內存分配malloc()/brk()等進程相關操作都需要分配內存給進程。不過這時進程申請和獲得的還不是實際內存,而是虛擬內存,准確的說是「內存區域」。進程對內存區域的分配最終都會歸結到do_mmap()函數上來(brk調用被單獨以系統調用實現,不用do_mmap()),

內核使用do_mmap()函數創建一個新的線性地址區間。但是說該函數創建了一個新VMA並不非常准確,因為如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問許可權的話,那麼兩個區間將合並為一個。如果不能合並,那麼就確實需要創建一個新的VMA了。但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中--無論是擴展已存在的內存區域還是創建一個新的區域。

同樣,釋放一個內存區域應使用函數do_ummap(),它會銷毀對應的內存區域。

如何由虛變實!

從上面已經看到進程所能直接操作的地址都為虛擬地址。當進程需要內存時,從內核獲得的僅僅是虛擬的內存區域,而不是實際的物理地址,進程並沒有獲得物理內存(物理頁面——頁的概念請大家參考硬體基礎一章),獲得的僅僅是對一個新的線性地址區間的使用權。實際的物理內存只有當進程真的去訪問新獲取的虛擬地址時,才會由「請求頁機制」產生「缺頁」異常,從而進入分配實際頁面的常式。

該異常是虛擬內存機制賴以存在的基本保證——它會告訴內核去真正為進程分配物理頁,並建立對應的頁表,這之後虛擬地址才實實在在地映射到了系統的物理內存上。(當然,如果頁被換出到磁碟,也會產生缺頁異常,不過這時不用再建立頁表了)

這種請求頁機制把頁面的分配推遲到不能再推遲為止,並不急於把所有的事情都一次做完(這種思想有點像設計模式中的代理模式(proxy))。之所以能這么做是利用了內存訪問的「局部性原理」,請求頁帶來的好處是節約了空閑內存,提高了系統的吞吐率。要想更清楚地了解請求頁機制,可以看看《深入理解linux內核》一書。

這里我們需要說明在內存區域結構上的nopage操作。當訪問的進程虛擬內存並未真正分配頁面時,該操作便被調用來分配實際的物理頁,並為該頁建立頁表項。在最後的例子中我們會演示如何使用該方法。

系統物理內存管理

雖然應用程序操作的對象是映射到物理內存之上的虛擬內存,但是處理器直接操作的卻是物理內存。所以當應用程序訪問一個虛擬地址時,首先必須將虛擬地址轉化成物理地址,然後處理器才能解析地址訪問請求。地址的轉換工作需要通過查詢頁表才能完成,概括地講,地址轉換需要將虛擬地址分段,使每段虛地址都作為一個索引指向頁表,而頁表項則指向下一級別的頁表或者指向最終的物理頁面。

每個進程都有自己的頁表。進程描述符的pgd域指向的就是進程的頁全局目錄。下面我們借用《linux設備驅動程序》中的一幅圖大致看看進程地址空間到物理頁之間的轉換關系。

上面的過程說起來簡單,做起來難呀。因為在虛擬地址映射到頁之前必須先分配物理頁——也就是說必須先從內核中獲取空閑頁,並建立頁表。下面我們介紹一下內核管理物理內存的機制。

物理內存管理(頁管理)

Linux內核管理物理內存是通過分頁機制實現的,它將整個內存劃分成無數個4k(在i386體系結構中)大小的頁,從而分配和回收內存的基本單位便是內存頁了。利用分頁管理有助於靈活分配內存地址,因為分配時不必要求必須有大塊的連續內存[3],系統可以東一頁、西一頁的湊出所需要的內存供進程使用。雖然如此,但是實際上系統使用內存時還是傾向於分配連續的內存塊,因為分配連續內存時,頁表不需要更改,因此能降低TLB的刷新率(頻繁刷新會在很大程度上降低訪問速度)。

鑒於上述需求,內核分配物理頁面時為了盡量減少不連續情況,採用了「夥伴」關系來管理空閑頁面。夥伴關系分配演算法大家應該不陌生——幾乎所有操作系統方面的書都會提到,我們不去詳細說它了,如果不明白可以參看有關資料。這里只需要大家明白Linux中空閑頁面的組織和管理利用了夥伴關系,因此空閑頁面分配時也需要遵循夥伴關系,最小單位只能是2的冪倍頁面大小。內核中分配空閑頁面的基本函數是get_free_page/get_free_pages,它們或是分配單頁或是分配指定的頁面(2、4、8…512頁)。

注意:get_free_page是在內核中分配內存,不同於malloc在用戶空間中分配,malloc利用堆動態分配,實際上是調用brk()系統調用,該調用的作用是擴大或縮小進程堆空間(它會修改進程的brk域)。如果現有的內存區域不夠容納堆空間,則會以頁面大小的倍數為單位,擴張或收縮對應的內存區域,但brk值並非以頁面大小為倍數修改,而是按實際請求修改。因此Malloc在用戶空間分配內存可以以位元組為單位分配,但內核在內部仍然會是以頁為單位分配的。

另外,需要提及的是,物理頁在系統中由頁結構structpage描述,系統中所有的頁面都存儲在數組mem_map[]中,可以通過該數組找到系統中的每一頁(空閑或非空閑)。而其中的空閑頁面則可由上述提到的以夥伴關系組織的空閑頁鏈表(free_area[MAX_ORDER])來索引。

內核內存使用

Slab

所謂尺有所長,寸有所短。以頁為最小單位分配內存對於內核管理系統中的物理內存來說的確比較方便,但內核自身最常使用的內存卻往往是很小(遠遠小於一頁)的內存塊——比如存放文件描述符、進程描述符、虛擬內存區域描述符等行為所需的內存都不足一頁。這些用來存放描述符的內存相比頁面而言,就好比是麵包屑與麵包。一個整頁中可以聚集多個這些小塊內存;而且這些小塊內存塊也和麵包屑一樣頻繁地生成/銷毀。

為了滿足內核對這種小內存塊的需要,Linux系統採用了一種被稱為slab分配器的技術。Slab分配器的實現相當復雜,但原理不難,其核心思想就是「存儲池[4]」的運用。內存片段(小塊內存)被看作對象,當被使用完後,並不直接釋放而是被緩存到「存儲池」里,留做下次使用,這無疑避免了頻繁創建與銷毀對象所帶來的額外負載。

Slab技術不但避免了內存內部分片(下文將解釋)帶來的不便(引入Slab分配器的主要目的是為了減少對夥伴系統分配演算法的調用次數——頻繁分配和回收必然會導致內存碎片——難以找到大塊連續的可用內存),而且可以很好地利用硬體緩存提高訪問速度。

Slab並非是脫離夥伴關系而獨立存在的一種內存分配方式,slab仍然是建立在頁面基礎之上,換句話說,Slab將頁面(來自於夥伴關系管理的空閑頁面鏈表)撕碎成眾多小內存塊以供分配,slab中的對象分配和銷毀使用kmem_cache_alloc與kmem_cache_free。

Kmalloc

Slab分配器不僅僅只用來存放內核專用的結構體,它還被用來處理內核對小塊內存的請求。當然鑒於Slab分配器的特點,一般來說內核程序中對小於一頁的小塊內存的請求才通過Slab分配器提供的介面Kmalloc來完成(雖然它可分配32到131072位元組的內存)。從內核內存分配的角度來講,kmalloc可被看成是get_free_page(s)的一個有效補充,內存分配粒度更靈活了。

有興趣的話,可以到/proc/slabinfo中找到內核執行現場使用的各種slab信息統計,其中你會看到系統中所有slab的使用信息。從信息中可以看到系統中除了專用結構體使用的slab外,還存在大量為Kmalloc而准備的Slab(其中有些為dma准備的)。

內核非連續內存分配(Vmalloc)

夥伴關系也好、slab技術也好,從內存管理理論角度而言目的基本是一致的,它們都是為了防止「分片」,不過分片又分為外部分片和內部分片之說,所謂內部分片是說系統為了滿足一小段內存區(連續)的需要,不得不分配了一大區域連續內存給它,從而造成了空間浪費;外部分片是指系統雖有足夠的內存,但卻是分散的碎片,無法滿足對大塊「連續內存」的需求。無論何種分片都是系統有效利用內存的障礙。slab分配器使得一個頁面內包含的眾多小塊內存可獨立被分配使用,避免了內部分片,節約了空閑內存。夥伴關系把內存塊按大小分組管理,一定程度上減輕了外部分片的危害,因為頁框分配不在盲目,而是按照大小依次有序進行,不過夥伴關系只是減輕了外部分片,但並未徹底消除。你自己比劃一下多次分配頁面後,空閑內存的剩餘情況吧。

所以避免外部分片的最終思路還是落到了如何利用不連續的內存塊組合成「看起來很大的內存塊」——這里的情況很類似於用戶空間分配虛擬內存,內存邏輯上連續,其實映射到並不一定連續的物理內存上。Linux內核借用了這個技術,允許內核程序在內核地址空間中分配虛擬地址,同樣也利用頁表(內核頁表)將虛擬地址映射到分散的內存頁上。以此完美地解決了內核內存使用中的外部分片問題。內核提供vmalloc函數分配內核虛擬內存,該函數不同於kmalloc,它可以分配較Kmalloc大得多的內存空間(可遠大於128K,但必須是頁大小的倍數),但相比Kmalloc來說,Vmalloc需要對內核虛擬地址進行重映射,必須更新內核頁表,因此分配效率上要低一些(用空間換時間)

與用戶進程相似,內核也有一個名為init_mm的mm_strcut結構來描述內核地址空間,其中頁表項pdg=swapper_pg_dir包含了系統內核空間(3G-4G)的映射關系。因此vmalloc分配內核虛擬地址必須更新內核頁表,而kmalloc或get_free_page由於分配的連續內存,所以不需要更新內核頁表。

vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核虛擬內存位於不同的區間,不會重疊。因為內核虛擬空間被分區管理,各司其職。進程空間地址分布從0到3G(其實是到PAGE_OFFSET,在0x86中它等於0xC0000000),從3G到vmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁面表mem_map等等)比如我使用的系統內存是64M(可以用free看到),那麼(3G——3G+64M)這片內存就應該映射到物理內存,而vmalloc_start位置應在3G+64M附近(說"附近"因為是在物理內存映射區與vmalloc_start期間還會存在一個8M大小的gap來防止躍界),vmalloc_end的位置接近4G(說"接近"是因為最後位置系統會保留一片128k大小的區域用於專用頁面映射,還有可能會有高端內存映射區,這些都是細節,這里我們不做糾纏)。

上圖是內存分布的模糊輪廓

由get_free_page或Kmalloc函數所分配的連續內存都陷於物理映射區域,所以它們返回的內核虛擬地址和實際物理地址僅僅是相差一個偏移量(PAGE_OFFSET),你可以很方便的將其轉化為物理內存地址,同時內核也提供了virt_to_phys()函數將內核虛擬空間中的物理映射區地址轉化為物理地址。要知道,物理內存映射區中的地址與內核頁表是有序對應的,系統中的每個物理頁面都可以找到它對應的內核虛擬地址(在物理內存映射區中的)。

而vmalloc分配的地址則限於vmalloc_start與vmalloc_end之間。每一塊vmalloc分配的內核虛擬內存都對應一個vm_struct結構體(可別和vm_area_struct搞混,那可是進程虛擬內存區域的結構),不同的內核虛擬地址被4k大小的空閑區間隔,以防止越界——見下圖)。與進程虛擬地址的特性一樣,這些虛擬地址與物理內存沒有簡單的位移關系,必須通過內核頁表才可轉換為物理地址或物理頁。它們有可能尚未被映射,在發生缺頁時才真正分配物理頁面。

這里給出一個小程序幫助大家認清上面幾種分配函數所對應的區域。

#include<linux/mole.h>

#include<linux/slab.h>

#include<linux/vmalloc.h>

unsignedchar*pagemem;

unsignedchar*kmallocmem;

unsignedchar*vmallocmem;

intinit_mole(void)

{

pagemem = get_free_page(0);

printk("<1>pagemem=%s",pagemem);

kmallocmem = kmalloc(100,0);

printk("<1>kmallocmem=%s",kmallocmem);

vmallocmem = vmalloc(1000000);

printk("<1>vmallocmem=%s",vmallocmem);

}

voidcleanup_mole(void)

{

free_page(pagemem);

kfree(kmallocmem);

vfree(vmallocmem);

}

實例

內存映射(mmap)是Linux操作系統的一個很大特色,它可以將系統內存映射到一個文件(設備)上,以便可以通過訪問文件內容來達到訪問內存的目的。這樣做的最大好處是提高了內存訪問速度,並且可以利用文件系統的介面編程(設備在Linux中作為特殊文件處理)訪問內存,降低了開發難度。許多設備驅動程序便是利用內存映射功能將用戶空間的一段地址關聯到設備內存上,無論何時,只要內存在分配的地址范圍內進行讀寫,實際上就是對設備內存的訪問。同時對設備文件的訪問也等同於對內存區域的訪問,也就是說,通過文件操作介面可以訪問內存。Linux中的X伺服器就是一個利用內存映射達到直接高速訪問視頻卡內存的例子。

熟悉文件操作的朋友一定會知道file_operations結構中有mmap方法,在用戶執行mmap系統調用時,便會調用該方法來通過文件訪問內存——不過在調用文件系統mmap方法前,內核還需要處理分配內存區域(vma_struct)、建立頁表等工作。對於具體映射細節不作介紹了,需要強調的是,建立頁表可以採用remap_page_range方法一次建立起所有映射區的頁表,或利用vma_struct的nopage方法在缺頁時現場一頁一頁的建立頁表。第一種方法相比第二種方法簡單方便、速度快,但是靈活性不高。一次調用所有頁表便定型了,不適用於那些需要現場建立頁表的場合——比如映射區需要擴展或下面我們例子中的情況。

我們這里的實例希望利用內存映射,將系統內核中的一部分虛擬內存映射到用戶空間,以供應用程序讀取——你可利用它進行內核空間到用戶空間的大規模信息傳輸。因此我們將試圖寫一個虛擬字元設備驅動程序,通過它將系統內核空間映射到用戶空間——將內核虛擬內存映射到用戶虛擬地址。從上一節已經看到Linux內核空間中包含兩種虛擬地址:一種是物理和邏輯都連續的物理內存映射虛擬地址;另一種是邏輯連續但非物理連續的vmalloc分配的內存虛擬地址。我們的例子程序將演示把vmalloc分配的內核虛擬地址映射到用戶地址空間的全過程。

程序里主要應解決兩個問題:

第一是如何將vmalloc分配的內核虛擬內存正確地轉化成物理地址?

因為內存映射先要獲得被映射的物理地址,然後才能將其映射到要求的用戶虛擬地址上。我們已經看到內核物理內存映射區域中的地址可以被內核函數virt_to_phys轉換成實際的物理內存地址,但對於vmalloc分配的內核虛擬地址無法直接轉化成物理地址,所以我們必須對這部分虛擬內存格外「照顧」——先將其轉化成內核物理內存映射區域中的地址,然後在用virt_to_phys變為物理地址。

轉化工作需要進行如下步驟:

  • 找到vmalloc虛擬內存對應的頁表,並尋找到對應的頁表項。

  • 獲取頁表項對應的頁面指針

  • 通過頁面得到對應的內核物理內存映射區域地址。

  • 如下圖所示:

    第二是當訪問vmalloc分配區時,如果發現虛擬內存尚未被映射到物理頁,則需要處理「缺頁異常」。因此需要我們實現內存區域中的nopaga操作,以能返回被映射的物理頁面指針,在我們的實例中就是返回上面過程中的內核物理內存映射區域中的地址。由於vmalloc分配的虛擬地址與物理地址的對應關系並非分配時就可確定,必須在缺頁現場建立頁表,因此這里不能使用remap_page_range方法,只能用vma的nopage方法一頁一頁的建立。

    程序組成

    map_driver.c,它是以模塊形式載入的虛擬字元驅動程序。該驅動負責將一定長的內核虛擬地址(vmalloc分配的)映射到設備文件上。其中主要的函數有——vaddress_to_kaddress()負責對vmalloc分配的地址進行頁表解析,以找到對應的內核物理映射地址(kmalloc分配的地址);map_nopage()負責在進程訪問一個當前並不存在的VMA頁時,尋找該地址對應的物理頁,並返回該頁的指針。

    test.c它利用上述驅動模塊對應的設備文件在用戶空間讀取讀取內核內存。結果可以看到內核虛擬地址的內容(ok!),被顯示在了屏幕上。

    執行步驟

    編譯map_driver.c為map_driver.o模塊,具體參數見Makefile

    載入模塊:insmodmap_driver.o

    生成對應的設備文件

    1在/proc/devices下找到map_driver對應的設備命和設備號:grepmapdrv/proc/devices

    2建立設備文件mknodmapfilec 254 0(在我的系統里設備號為254)

    利用maptest讀取mapfile文件,將取自內核的信息列印到屏幕上。

    Ⅱ linux kernel可不可以完成進程調度

    操作系統要實現多進程,進程調度必不可少。 有人說,進程調度是操作系統中最為重要的一個部分。我覺得這種說法說得太絕對了一點,就像很多人動輒就說"某某函數比某某函數效率高XX倍"一樣,脫離了實際環境,這些結論是比較片面的。 而進程調度究竟有多重要呢? 首先,我們需要明確一點:進程調度是對TASK_RUNNING狀態的進程進行調度(參見《linux進程狀態淺析》)。如果進程不可執行(正在睡眠或其他),那麼它跟進程調度沒多大關系。 所以,如果你的系統負載非常低,盼星星盼月亮才出現一個可執行狀態的進程。那麼進程調度也就不會太重要。哪個進程可執行,就讓它執行去,沒有什麼需要多考慮的。 反之,如果系統負載非常高,時時刻刻都有N多個進程處於可執行狀態,等待被調度運行。那麼進程調度程序為了協調這N個進程的執行,必定得做很多工作。協調得不好,系統的性能就會大打折扣。這個時候,進程調度就是非常重要的。 盡管我們平常接觸的很多計算機(如桌面系統、網路伺服器、等)負載都比較低,但是linux作為一個通用操作系統,不能假設系統負載低,必須為應付高負載下的進程調度做精心的設計。 當然,這些設計對於低負載(且沒有什麼實時性要求)的環境,沒多大用。極端情況下,如果CPU的負載始終保持0或1(永遠都只有一個進程或沒有進程需要在CPU上運行),那麼這些設計基本上都是徒勞的。 優先順序 現在的操作系統為了協調多個進程的“同時”運行,最基本的手段就是給進程定義優先順序。定義了進程的優先順序,如果有多個進程同時處於可執行狀態,那麼誰優先順序高誰就去執行,沒有什麼好糾結的了。 那麼,進程的優先順序該如何確定呢?有兩種方式:由用戶程序指定、由內核的調度程序動態調整。(下面會說到) linux內核將進程分成兩個級別:普通進程和實時進程。實時進程的優先順序都高於普通進程,除此之外,它們的調度策略也有所不同。 實時進程的調度 實時,原本的涵義是“給定的操作一定要在確定的時間內完成”。重點並不在於操作一定要處理得多快,而是時間要可控(在最壞情況下也不能突破給定的時間)。 這樣的“實時”稱為“硬實時”,多用於很精密的系統之中(比如什麼火箭、導彈之類的)。一般來說,硬實時的系統是相對比較專用的。 像linux這樣的通用操作系統顯然沒法滿足這樣的要求,中斷處理、虛擬內存、等機制的存在給處理時間帶來了很大的不確定性。硬體的cache、磁碟尋道、匯流排爭用、也會帶來不確定性。 比如考慮“i++;”這么一句C代碼。絕大多數情況下,它執行得很快。但是極端情況下還是有這樣的可能: 1、i的內存空間未分配,CPU觸發缺頁異常。而linux在缺頁異常的處理代碼中試圖分配內存時,又可能由於系統內存緊缺而分配失敗,導致進程進入睡眠; 2、代碼執行過程中硬體產生中斷,linux進入中斷處理程序而擱置當前進程。而中斷處理程序的處理過程中又可能發生新的硬體中斷,中斷永遠嵌套不止……; 等等…… 而像linux這樣號稱實現了“實時”的通用操作系統,其實只是實現了“軟實時”,即盡可能地滿足進程的實時需求。 如果一個進程有實時需求(它是一個實時進程),則只要它是可執行狀態的,內核就一直讓它執行,以盡可能地滿足它對CPU的需要,直到它完成所需要做的事情,然後睡眠或退出(變為非可執行狀態)。 而如果有多個實時進程都處於可執行狀態,則內核會先滿足優先順序最高的實時進程對CPU的需要,直到它變為非可執行狀態。 於是,只要高優先順序的實時進程一直處於可執行狀態,低優先順序的實時進程就一直不能得到CPU;只要一直有實時進程處於可執行狀態,普通進程就一直不能得到CPU。 那麼,如果多個相同優先順序的實時進程都處於可執行狀態呢?這時就有兩種調度策略可供選擇: 1、SCHED_FIFO:先進先出。直到先被執行的進程變為非可執行狀態,後來的進程才被調度執行。在這種策略下,先來的進程可以執行sched_yield系統調用,自願放棄CPU,以讓權給後來的進程; 2、SCHED_RR:輪轉調度。內核為實時進程分配時間片,在時間片用完時,讓下一個進程使用CPU; 強調一下,這兩種調度策略以及sched_yield系統調用都僅僅針對於相同優先順序的多個實時進程同時處於可執行狀態的情況。 在linux下,用戶程序可以通過sched_setscheler系統調用來設置進程的調度策略以及相關調度參數;sched_setparam系統調用則只用於設置調度參數。這兩個系統調用要求用戶進程具有設置進程優先順序的能力(CAP_SYS_NICE,一般來說需要root許可權)(參閱capability相關的文章)。 通過將進程的策略設為SCHED_FIFO或SCHED_RR,使得進程變為實時進程。而進程的優先順序則是通過以上兩個系統調用在設置調度參數時指定的。 對於實時進程,內核不會試圖調整其優先順序。因為進程實時與否?有多實時?這些問題都是跟用戶程序的應用場景相關,只有用戶能夠回答,內核不能臆斷。 綜上所述,實時進程的調度是非常簡單的。進程的優先順序和調度策略都由用戶定死了,內核只需要總是選擇優先順序最高的實時進程來調度執行即可。唯一稍微麻煩一點的只是在選擇具有相同優先順序的實時進程時,要考慮兩種調度策略。 普通進程的調度 實時進程調度的中心思想是,讓處於可執行狀態的最高優先順序的實時進程盡可能地佔有CPU,因為它有實時需求;而普通進程則被認為是沒有實時需求的進程,於是調度程序力圖讓各個處於可執行狀態的普通進程和平共處地分享CPU,從而讓用戶覺得這些進程是同時運行的。 與實時進程相比,普通進程的調度要復雜得多。內核需要考慮兩件麻煩事: 一、動態調整進程的優先順序 按進程的行為特徵,可以將進程分為“互動式進程”和“批處理進程”: 互動式進程(如桌面程序、伺服器、等)主要的任務是與外界交互。這樣的進程應該具有較高的優先順序,它們總是睡眠等待外界的輸入。而在輸入到來,內核將其喚醒時,它們又應該很快被調度執行,以做出響應。比如一個桌面程序,如果滑鼠點擊後半秒種還沒反應,用戶就會感覺系統“卡”了; 批處理進程(如編譯程序)主要的任務是做持續的運算,因而它們會持續處於可執行狀態。這樣的進程一般不需要高優先順序,比如編譯程序多運行了幾秒種,用戶多半不會太在意; 如果用戶能夠明確知道進程應該有怎樣的優先順序,可以通過nice、setpriority系統調用來對優先順序進行設置。(如果要提高進程的優先順序,要求用戶進程具有CAP_SYS_NICE能力。) 然而應用程序未必就像桌面程序、編譯程序這樣典型。程序的行為可能五花八門,可能一會兒像互動式進程,一會兒又像批處理進程。以致於用戶難以給它設置一個合適的優先順序。 再者,即使用戶明確知道一個進程是互動式還是批處理,也多半礙於許可權或因為偷懶而不去設置進程的優先順序。(你又是否為某個程序設置過優先順序呢?) 於是,最終,區分互動式進程和批處理進程的重任就落到了內核的調度程序上。 調度程序關注進程近一段時間內的表現(主要是檢查其睡眠時間和運行時間),根據一些經驗性的公式,判斷它現在是互動式的還是批處理的?程度如何?最後決定給它的優先順序做一定的調整。 進程的優先順序被動態調整後,就出現了兩個優先順序: 1、用戶程序設置的優先順序(如果未設置,則使用默認值),稱為靜態優先順序。這是進程優先順序的基準,在進程執行的過程中往往是不改變的; 2、優先順序動態調整後,實際生效的優先順序。這個值是可能時時刻刻都在變化的; 二、調度的公平性 在支持多進程的系統中,理想情況下,各個進程應該是根據其優先順序公平地佔有CPU。而不會出現“誰運氣好誰佔得多”這樣的不可控的情況。 linux實現公平調度基本上是兩種思路: 1、給處於可執行狀態的進程分配時間片(按照優先順序),用完時間片的進程被放到“過期隊列”中。等可執行狀態的進程都過期了,再重新分配時間片; 2、動態調整進程的優先順序。隨著進程在CPU上運行,其優先順序被不斷調低,以便其他優先順序較低的進程得到運行機會; 後一種方式有更小的調度粒度,並且將“公平性”與“動態調整優先順序”兩件事情合而為一,大大簡化了內核調度程序的代碼。因此,這種方式也成為內核調度程序的新寵。 強調一下,以上兩點都是僅針對普通進程的。而對於實時進程,內核既不能自作多情地去動態調整優先順序,也沒有什麼公平性可言。 普通進程具體的調度演算法非常復雜,並且隨linux內核版本的演變也在不斷更替(不僅僅是簡單的調整),所以本文就不繼續深入了。 調度程序的效率 “優先順序”明確了哪個進程應該被調度執行,而調度程序還必須要關心效率問題。調度程序跟內核中的很多過程一樣會頻繁被執行,如果效率不濟就會浪費很多CPU時間,導致系統性能下降。 在linux 2.4時,可執行狀態的進程被掛在一個鏈表中。每次調度,調度程序需要掃描整個鏈表,以找出最優的那個進程來運行。復雜度為O(n); 在linux 2.6早期,可執行狀態的進程被掛在N(N=140)個鏈表中,每一個鏈表代表一個優先順序,系統中支持多少個優先順序就有多少個鏈表。每次調度,調度程序只需要從第一個不為空的鏈表中取出位於鏈表頭的進程即可。這樣就大大提高了調度程序的效率,復雜度為O(1); 在linux 2.6近期的版本中,可執行狀態的進程按照優先順序順序被掛在一個紅黑樹(可以想像成平衡二叉樹)中。每次調度,調度程序需要從樹中找出優先順序最高的進程。復雜度為O(logN)。 那麼,為什麼從linux 2.6早期到近期linux 2.6版本,調度程序選擇進程時的復雜度反而增加了呢? 這是因為,與此同時,調度程序對公平性的實現從上面提到的第一種思路改變為第二種思路(通過動態調整優先順序實現)。而O(1)的演算法是基於一組數目不大的鏈表來實現的,按我的理解,這使得優先順序的取值范圍很小(區分度很低),不能滿足公平性的需求。而使用紅黑樹則對優先順序的取值沒有限制(可以用32位、64位、或更多位來表示優先順序的值),並且O(logN)的復雜度也還是很高效的。 調度觸發的時機 調度的觸發主要有如下幾種情況: 1、當前進程(正在CPU上運行的進程)狀態變為非可執行狀態。 進程執行系統調用主動變為非可執行狀態。比如執行nanosleep進入睡眠、執行exit退出、等等; 進程請求的資源得不到滿足而被迫進入睡眠狀態。比如執行read系統調用時,磁碟高速緩存里沒有所需要的數據,從而睡眠等待磁碟IO; 進程響應信號而變為非可執行狀態。比如響應SIGSTOP進入暫停狀態、響應SIGKILL退出、等等; 2、搶占。進程運行時,非預期地被剝奪CPU的使用權。這又分兩種情況:進程用完了時間片、或出現了優先順序更高的進程。 優先順序更高的進程受正在CPU上運行的進程的影響而被喚醒。如發送信號主動喚醒,或因為釋放互斥對象(如釋放鎖)而被喚醒; 內核在響應時鍾中斷的過程中,發現當前進程的時間片用完; 內核在響應中斷的過程中,發現優先順序更高的進程所等待的外部資源的變為可用,從而將其喚醒。比如CPU收到網卡中斷,內核處理該中斷,發現某個socket可讀,於是喚醒正在等待讀這個socket的進程;再比如內核在處理時鍾中斷的過程中,觸發了定時器,從而喚醒對應的正在nanosleep系統調用中睡眠的進程。 所有任務都採用linux分時調度策略時: 1,創建任務指定採用分時調度策略,並指定優先順序nice值(-20~19)。 2,將根據每個任務的nice值確定在cpu上的執行時間(counter)。 3,如果沒有等待資源,則將該任務加入到就緒隊列中。 4,調度程序遍歷就緒隊列中的任務,通過對每個任務動態優先順序的計算權值(counter+20-nice)結果,選擇計算結果最大的一個去運行,當這個時間片用完後(counter減至0)或者主動放棄cpu時,該任務將被放在就緒隊列末尾(時間片用完)或等待隊列(因等待資源而放棄cpu)中。 5,此時調度程序重復上面計算過程,轉到第4步。 6,當調度程序發現所有就緒任務計算所得的權值都為不大於0時,重復第2步。 所有任務都採用FIFO時: 1,創建進程時指定採用FIFO,並設置實時優先順序rt_priority(1-99)。 2,如果沒有等待資源,則將該任務加入到就緒隊列中。 3,調度程序遍歷就緒隊列,根據實時優先順序計算調度權值(1000+rt_priority),選擇權值最高的任務使用cpu,該FIFO任務將一直佔有cpu直到有優先順序更高的任務就緒(即使優先順序相同也不行)或者主動放棄(等待資源)。 4,調度程序發現有優先順序更高的任務到達(高優先順序任務可能被中斷或定時器任務喚醒,再或被當前運行的任務喚醒,等等),則調度程序立即在當前任務堆棧中保存當前cpu寄存器的所有數據,重新從高優先順序任務的堆棧中載入寄存器數據到cpu,此時高優先順序的任務開始運行。重復第3步。 5,如果當前任務因等待資源而主動放棄cpu使用權,則該任務將從就緒隊列中刪除,加入等待隊列,此時重復第3步。 所有任務都採用RR調度策略時: 1,創建任務時指定調度參數為RR,並設置任務的實時優先順序和nice值(nice值將會轉換為該任務的時間片的長度)。 2,如果沒有等待資源,則將該任務加入到就緒隊列中。 3,調度程序遍歷就緒隊列,根據實時優先順序計算調度權值(1000+rt_priority),選擇權值最高的任務使用cpu。 4,如果就緒隊列中的RR任務時間片為0,則會根據nice值設置該任務的時間片,同時將該任務放入就緒隊列的末尾。重復步驟3。 5,當前任務由於等待資源而主動退出cpu,則其加入等待隊列中。重復步驟3。 系統中既有分時調度,又有時間片輪轉調度和先進先出調度: 1,RR調度和FIFO調度的進程屬於實時進程,以分時調度的進程是非實時進程。 2,當實時進程准備就緒後,如果當前cpu正在運行非實時進程,則實時進程立即搶占非實時進程。 3,RR進程和FIFO進程都採用實時優先順序做為調度的權值標准,RR是FIFO的一個延伸。FIFO時,如果兩個進程的優先順序一樣,則這兩個優先順序一樣的進程具體執行哪一個是由其在隊列中的未知決定的,這樣導致一些不公正性(優先順序是一樣的,為什麼要讓你一直運行?),如果將兩個優先順序一樣的任務的調度策略都設為RR,則保證了這兩個任務可以循環執行,保證了公平。 Ingo Molnar-實時補丁 為了能並入主流內核,Ingo Molnar的實時補丁也採用了非常靈活的策略,它支持四種搶占模式: 1.No Forced Preemption (Server),這種模式等同於沒有使能搶占選項的標准內核,主要適用於科學計算等伺服器環境。 2.Voluntary Kernel Preemption (Desktop),這種模式使能了自願搶占,但仍然失效搶占內核選項,它通過增加搶占點縮減了搶占延遲,因此適用於一些需要較好的響應性的環境,如桌面環境,當然這種好的響應性是以犧牲一些吞吐率為代價的。 3.Preemptible Kernel (Low-Latency Desktop),這種模式既包含了自願搶占,又使能了可搶占內核選項,因此有很好的響應延遲,實際上在一定程度上已經達到了軟實時性。它主要適用於桌面和一些嵌入式系統,但是吞吐率比模式2更低。 4.Complete Preemption (Real-Time),這種模式使能了所有實時功能,因此完全能夠滿足軟實時需求,它適用於延遲要求為100微秒或稍低的實時系統。 實現實時是以犧牲系統的吞吐率為代價的,因此實時性越好,系統吞吐率就越低。

    Ⅲ 詳解Linux系統內存知識及調優方案

    內存是計算機中重要的部件之一,它是與CPU進行溝通的橋梁。計算機中所有程序的運行都是在內存中進行的,因此內存的性能對計算機的影響非常大。內存作用是用於暫時存放CPU中的運算數據,以及與硬碟等外部存儲器交換的數據。只要計算機在運行中,CPU就會把需要運算的數據調到內存中進行運算,當運算完成後CPU再將結果傳送出來,內存的運行也決定了計算機的穩定運行。對於整個操作系統來說,內存可能是最麻煩的的設備。而其性能的好壞直接影響著整個操作系統。

    我們知道CPU是不能與硬碟打交道的,只有數據被載入到內存中才可以被CPU調用。cpu在訪問內存的時候需要先像內存監控程序請求,由監控程序控制和分配內存的讀寫請求,這個監控程序叫做MMU(內存管理單元)。下面以32位系統來說明內存的訪問過程:

    32位的系統上每一個進程在訪問內存的時候,每一個進程都當做自己有4個G的內存空間可用,這叫虛擬內存(地址),虛擬內存轉化成物理內存是通過MMU來完成的。為了能夠從線性地址轉換成物理地址,需要page table(頁表)的內存空間,page table要載入到MMU上。為了完成線性地址到物理地址的映射,如果按照1個位元組1個位元組映射的話,需要一張非常大的表,這種轉換關系會非常的復雜。因此把內存空間又劃分成了另外一種存儲單元格式,通常為4K。在不同的硬體平台上,它們的大小一般是不一樣的,像x86 32位的有4k的頁;而64位的有4k頁,2M頁,4M頁,8M頁等等,默認都是4k的。每一個進程一般而言都有自己的頁路徑和頁表映射機制,不管那一個頁表都是由內核載入的。每一個進程只能看到自己的線性地址空間,想要增加新的內存的時候,只能在自己的線性地址空間中申請,並且申請後一定是通過操作系統的內核映射到物理地址空間中去找那麼一段空間,並且告訴線性地址空間准備好了,可以訪問,並且在page table中增加一條映射關系,於是就可以訪問物理內存了,這種叫做內存分配。但是新的申請一定是通過操作的內核到物理內存中去找那麼一段空間,並且告訴線性地址空間好了,可以建設映射關系,最終page table建立映射關系。

    這反映了上述描述過程的大體情況。可以看到每一個用戶程序都會有自己的頁表,並且映射到對應的主存儲器上去。

    根據上述文字和圖表的描述可以發現2個問題:

    1.每個進程如果需要訪問內存的時候都需要去查找page table的話,勢必會造成伺服器的性能底下

    2.如果主存儲器的內存滿了以後,應用程序還需要調用內存的時候怎麼辦

    對於第一個問題,我們就需要藉助TLB(Translation Lookaside Buffer)翻譯後備緩沖器。TLB是一個內存管理單元,它可以用於改進虛擬地址到物理地址轉換速度的緩存。這樣每次在查找page table的時候就可以先去TLB中查找相應的頁表數據,如果有就直接返回,沒有再去查找page table,並把查找到的結果緩存中TLB中。TLB雖然解決了緩存的功能,但是在那麼page table中查找映射關系仍然很慢,所以又有了page table的分級目錄。page table可以分為1級目錄,2級目錄和偏移量

    但是一個進程在運行的時候要頻繁的打開文件,關閉文件。這就意味著要頻繁的申請內存和釋放內存。有些能夠在內存中緩存數據的那些進程,他們對內存的分配和回收更多,那麼每一次分配都會在頁表中建立一個對應項。所以,就算內存的速度很快,大量頻繁的同一時間分配和釋放內存,依然會降低伺服器的整體性能。當然內存空間不夠用的時候,我們稱為oom(out of memory,內存耗盡)。當內存耗盡的時候,,整個操作系統掛了。這種情況下我們可以考慮交換分區,交換分區畢竟是由硬碟虛擬出來的內存,所以其性能與真正的內存相比,差了很多,所以要盡力避免使用交換分區。有物理內存空間的時候盡量保證全部使用物理內存。cpu無論如何是不能給交換內存打交道的,它也只能給物理內存打交道,能定址的空間也只能是物理內存。所以當真正物理內存空間不夠用的時候,會通過LRU演算法把其中最近最少使用的內存放到交換內存中去,這樣物理內存中的那段空間就可以供新的程序使用了。但是這樣會引發另外的一個問題,即原來的進程通過page table尋找的時候,那一段空間的數據已經不屬於它了。所以此刻cpu發送通知或者異常告訴這個程序,這個地址空間已不屬於它,這個時候可能會出現2種情況:

    1.物理內存有可用的空間可用:這個時候cpu會根據以前的轉換策略會把交換分區中的那段內存重新送到物理內存中去,但是轉換過來的空間地址不一定會是以前的那一段空間地址,因為以前的那一段空間地址可能已經被別人使用了。

    2.物理內存沒有可用的空間可用:這個時候依然會使用LRU算發把當前物理地址空間上最近最少使用的空間地址轉換到交換內存中去,並把當前進程需要的這斷在交換空間中的內存送到物理內存空間中去,並且重新建立映射關系。

    上述通知或者異常出現的情況,通常叫做缺頁異常。缺頁異常也分為大異常和小異常兩種。大異常就是訪問的數據內存中沒有,不的不去硬碟上載入,無論是從交換內存中還是直接從磁碟的某個文件系統上,反正需要從硬碟上去載入,這種異常載入需要很長時間。小異常就是進程之間通過共享內存,第二個進程訪問的時候,查看本地的內存映射表沒有,但是其它進程已經擁有了這個內存頁,所以可以直接映射,這種異常載入需要的時間一般很短。

    在操作系統開機的時候,每一個io設備都會像cpu申請一些列的隨機埠,這種埠叫做io埠。在IBM PC體系結構中,I/O地址空間一共提供了65,536個8位的I/O埠。正是這些io埠的存在,cpu可以與io設備進行讀寫交互的過程。在執行讀寫操作時,CPU使用地址匯流排選擇所請求的I/O埠,使用數據匯流排在CPU寄存器和埠之間傳送數據。I/O埠還可以被映射到物理地址空間:因此,處理器和I/O設備之間的通信就可以直接使用對內存進行操作的匯編語言指令(例如,mov、and、or等等)。現代的硬體設備更傾向於映射I/O,因為這樣處理的速度較快,並可以和DMA結合起來使用。這樣io在和內存傳數據的時候就不需要通過cpu,cpu把匯流排的控制權交給DMA,每次io傳數據的時候就調用DMA一次,就把cpu給解放了出來。當數據傳輸完了以後,DMA通知給cpu中斷一次。DMA在運行的時候對整個匯流排有控制許可權,當cpu發現有其它進程需要使用匯流排的時候,二者就會產生爭用。這個時候,在匯流排控制權的使用上,CPU和DMA具有相等的許可權。只要CPU委託給了DMA,就不能隨意的收回這個委託,就要等待DMA的用完。

    如果沒有其它進程可以運行,或者其它進程運行的時間非常短,這個時候CPU發現我們的IO仍然沒有完成,那就意味著,CPU只能等待IO了。CPU在時間分配裡面有個iowait的值,就是CPU在等待IO花費的時間。有些是在同步調用過程中,CPU必須要等待IO的完成;否者CPU可以釋放IO的傳輸在背後自動完成,CPU自己去處理其它的事情。等硬碟數據傳輸完成以後,硬碟只需要像CPU發起一個通知即可。CPU外圍有一種設備,這個設備叫做可編程中斷控制器。每一個硬體設備為了給CPU通信,在剛開機的時候,在BIOS實現檢測的時候,這個設備就要到可編程中斷控制器上去注冊一個所謂的中斷號。那麼這個號碼就歸這個硬體使用了。當前主機上可能有多個硬體,每一個硬體都有自己的號碼,CPU在收到中斷號以後,就能夠通過中斷相量表查找到那個硬體設備進行中斷。並且就由對應的IO埠過來處理了。

    CPU正在運行其它進程,當一個中斷請求發過來的時候,CPU會立即終止當前正在處理的進程,而去處理中斷。當前CPU掛起當前正在處理的進程,轉而去執行中斷的過程,也叫做中斷切換。只不過,這種切換在量級別上比進程切換要低一些,而且任何中斷的優先順序通常比任何進程也要高,因為我們指的是硬體中斷。中斷還分為上半部和下半部,一般而言,上半部就是CPU在處理的時候,把它接進來,放到內存中,如果這個事情不是特別緊急(CPU或者內核會自己判斷),因此在這種情況下,CPU回到現場繼續執行剛才掛起的進程,當這個進程處理完了,再回過頭來執行中斷的下半部分。

    在32位系統中,我們的內存(線性地址)地址空間中,一般而言,低地址空間有一個G是給內核使用的,上面3個G是給進程使用的。但是應該明白,其實在內核內存當中,再往下,不是直接這樣劃分的。32位系統和64位系統可能不一樣(物理地址),在32位系統中,最低端有那麼10多M的空間是給DMA使用的。DNA的匯流排寬度是很小的,可能只有幾位,所以定址能力很有限,訪問的內存空間也就很有限。如果DMA需要復制數據,而且自己能夠定址物理內存,還可以把數據直接壯哉進內存中去,那麼就必須保證DMA能夠定址那段內存才行。定址的前提就是把最低地址斷M,DA的定址范圍內的那一段給了DMA。所以站在這個角度來說,我們的內存管理是分區域的。

    在32位系統上,16M的內存空間給了ZONE_DMA(DMA使用的物理地址空間);從16M到896M給了ZONE_NORMAL(正常物理地址空間),對於Linux操作系統來說,是內核可以直接訪問的地址空間;從896M到1G這斷空間叫做"Reserved"(預留的物理地址空間);從1G到4G的這段物理地址空間中,我們的內核是不能直接訪問的,要想訪問必須把其中的一段內容映射到Reserved來,在Reserved中保留出那一段內存的地址編碼,我們內核才能上去訪問,所以內核不直接訪問大於1G的物理地址空間。所以在32位系統上,它訪問內存當中的數據,中間是需要一個額外步驟的。

    在64位系統上,ZONE_DAM給了低端的1G地址空間,這個時候DMA的定址能力被大大加強了;ZONE_DAM32可以使用4G的空間;而大於1G以上給劃分了ZONE_NORMAL,這段空間都可以被內核直接訪問。所以在64位上,內核訪問大於1G的內存地址,就不需要額外的步驟了,效率和性能上也大大增加,這也就是為什麼要使用64位系統的原因。

    在現在的PC架構上,AMD,INTER都支持一種機制,叫做PEA(物理地址擴展)。所謂PAE。指的是在32位系統的地址匯流排上,又擴展了4位,使得32位系統上的地址空間可以達到64G。當然在32為系統上,不管你的物理內存有多大,單個進程所使用的空間是無法擴展的。因為在32位的系統上,線性地址空間只有4個G,而單個進程能夠識別的訪問也只有3個G。

    linux的虛擬內存子系統包含了以下幾個功能模塊:

    slab allocator,zoned buddy allocator,MMU,kswapd,bdflush

    slab allocator叫做slab分配器

    buddy allocator又叫做buddy system,叫做夥伴系統,也是一種內存分配器

    buddy system是工作在MMU之上的,而slab allocator又是工作在buddy system之上的。

    設置為小於等於1G,在資料庫伺服器應該勁量避免使用交換內存

    3.在應用伺服器上,可以設置為RAM*0.5,當然這個是理論值

    如果不的不使用交換內存,應該把交換內存放到最靠外的磁軌分區上,因為最外邊的磁碟的訪問速度最快。所以如果有多塊硬碟,可以把每塊硬碟的最外層的磁軌拿一小部分出來作為交換分區。交換分區可以定義優先順序,因此把這些硬碟的交換內存的優先順序設置為一樣,可以實現負載均衡的效果。定義交換分區優先順序的方法為編輯/etc/fstab:

    /dev/sda1 swap swap pri=5 0 0

    /dev/sdb1 swap swap pri=5 0 0

    /dev/sdc1 swap swap pri=5 0 0

    /dev/sdd1 swap swap pri=5 0 0

    四.內存耗盡時候的相關調優參數

    當Linux內存耗盡的時候,它會殺死那些佔用內存最多的進程,以下三種情況會殺死進程:

    1.所有的進程都是活動進程,這個時候想交換出去都沒有空閑的進程

    2.沒有可用的page頁在ZONE_NORMAL中

    3.有其它新進程啟動,申請內存空間的時候,要找一個空閑內存給做映射,但是這個時候找不到了

    一旦內存耗盡的時候,操作系統就會啟用oom-kill機制。

    在/proc/PID/目錄下有一個文件叫做oom_score,就是用來指定oom的評分的,就是壞蛋指數。

    如果要手動啟用oom-kill機制的話,只需要執行echo f>/proc/sysrq-trigger即可,它會自動殺掉我們指定的壞蛋指數評分最高的那個進程

    可以通過echo n > /proc/PID/oom_adj來調整一個進程的壞蛋評分指數。最終的評分指數就是2的oom_adj的值的N次方。假如我們的一個進程的oom_adj的值是5,那麼它的壞蛋評分指數就是2的5次方。

    如果想禁止oom-kill功能的使用可以使用vm.panic_on_oom=1即可。

    五.與容量有關的內存調優參數:

    overcommit_memory,可用參數有3個,規定是否能夠過量使用內存:

    0:默認設置,內核執行啟發式的過量使用處理

    1:內核執行無內存的過量使用處理。使用這個值會增大內存超載的可能性

    2:內存的使用量等於swap的大小+RAM*overcommit_ratio的值。如果希望減小內存的過度使用,這個值是最安全的

    overcommit_ratio:將overcommit_memory指定為2時候,提供的物理RAM比例,默認為50

    六.與通信相關的調優參數

    常見在同一個主機中進行進程間通信的方式:

    1.通過消息message;2.通過signal信號量進行通信;3.通過共享內存進行通信,跨主機常見的通信方式是rpc

    以消息的方式實現進程通信的調優方案:

    msgmax:以位元組為單位規定消息隊列中任意消息的最大允許大小。這個值一定不能超過該隊列的大小(msgmnb),默認值為65536

    msgmnb:以位元組為單位規定單一消息隊列的最大值(最大長度)。默認為65536位元組

    msgmni:規定消息隊列識別符的最大數量(及隊列的最大數量)。64位架構機器的默認值為1985;32位架構機器的默認值為1736

    以共享內存方式實現進程通信的調優方案:

    shmall:以位元組為單位規定一次在該系統中可以使用的共享內存總量(單次申請的上限)

    shmmax:以位元組為單位規定每一個共享內存片段的最大大小

    shmmni:規定系統范圍內最大共享內存片段。在64和32位的系統上默認值都是4096

    七.與容量相關的文件系統可調優參數:

    file-max:列出內核分配的文件句柄的最大值

    dirty_ratio:規定百分比值,當臟數據達到系統內存總數的這個百分比值後開始執行pdflush,默認為20

    dirty_background_ratio:規定百分比值,當某一個進程自己所佔用的臟頁比例達到系統內存總數的這個百分比值後開始在後台執行pdflush,默認為10

    dirty_expire_centisecs:pdlush每隔百分之一秒的時間開啟起來刷新臟頁,默認值為3000,所以每隔30秒起來開始刷新臟頁

    dirty_writeback_centisecs:每隔百分之一秒開始刷新單個臟頁。默認值為500,所以一個臟頁的存在時間達到了5秒,就開始刷新臟

    八.linux內存常用的觀察指標命令

    Memory activity

    vmstat [interval] [count]

    sar -r [interval] [count]

    Rate of change in memory

    sar -R [interval] [count]

    frmpg/s:每秒釋放或者分配的內存頁,如果為正數,則為釋放的內存頁;如果為負數,則為分配的內存頁

    bufpg/s:每秒buffer中獲得或者釋放的內存頁。如果為正數則為獲得的內存頁,為負數。則為釋放的內存頁

    campg/s:每秒cache中獲得或者釋放的內存頁。如果為正數則為獲得的內存頁,為負數。則為釋放的內存頁

    Swap activity

    sar -W [interval] [count]

    ALL IO

    sar -B [interval] [count]

    pgpgin/s:每秒從磁碟寫入到內核的塊數量

    pgpgout/s:每秒從內核寫入到磁碟的塊數量

    fault/s:每秒鍾出現的缺頁異常的個數

    majflt/s:每秒鍾出現的大頁異常的個數

    pgfree/s:每秒回收回來的頁面個數

    Ⅳ Linux進程間通信

    linux下進程間通信的幾種主要手段簡介:

    一般文件的I/O函數都可以用於管道,如close、read、write等等。

    實例1:用於shell

    管道可用於輸入輸出重定向,它將一個命令的輸出直接定向到另一個命令的輸入。比如,當在某個shell程序(Bourne shell或C shell等)鍵入who│wc -l後,相應shell程序將創建who以及wc兩個進程和這兩個進程間的管道。

    實例二:用於具有親緣關系的進程間通信

    管道的主要局限性正體現在它的特點上:

    有名管道的創建

    小結:

    管道常用於兩個方面:(1)在shell中時常會用到管道(作為輸入輸入的重定向),在這種應用方式下,管道的創建對於用戶來說是透明的;(2)用於具有親緣關系的進程間通信,用戶自己創建管道,並完成讀寫操作。

    FIFO可以說是管道的推廣,克服了管道無名字的限制,使得無親緣關系的進程同樣可以採用先進先出的通信機制進行通信。

    管道和FIFO的數據是位元組流,應用程序之間必須事先確定特定的傳輸"協議",採用傳播具有特定意義的消息。

    要靈活應用管道及FIFO,理解它們的讀寫規則是關鍵。

    信號生命周期

    信號是進程間通信機制中唯一的非同步通信機制,可以看作是非同步通知,通知接收信號的進程有哪些事情發生了。信號機制經過POSIX實時擴展後,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。

    可以從兩個不同的分類角度對信號進行分類:(1)可靠性方面:可靠信號與不可靠信號;(2)與時間的關繫上:實時信號與非實時信號。

    (1) 可靠信號與不可靠信號

    不可靠信號 :Linux下的不可靠信號問題主要指的是信號可能丟失。

    可靠信號 :信號值位於SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()。

    對於目前linux的兩個信號安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信號變成可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以後的信號都支持排隊。這兩個函數的最大區別在於,經過sigaction安裝的信號都能傳遞信息給信號處理函數(對所有信號這一點都成立),而經過signal安裝的信號卻不能向信號處理函數傳遞信息。對於信號發送函數來說也是一樣的。

    (2) 實時信號與非實時信號

    前32種信號已經有了預定義值,每個信號有了確定的用途及含義,並且每種信號都有各自的預設動作。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。後32個信號表示實時信號,等同於前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。實時信號是POSIX標準的一部分,可用於應用進程。非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。

    發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

    調用成功返回 0;否則,返回 -1。

    sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。

    sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4位元組值。

    sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號。sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號。

    inux主要有兩個函數實現信號的安裝: signal() sigaction() 。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現在支持信號帶有參數。

    消息隊列就是一個消息的鏈表。可以把消息看作一個記錄,具有特定的格式以及特定的優先順序。對消息隊列有寫許可權的進程可以向中按照一定的規則添加新消息;對消息隊列有讀許可權的進程則可以從消息隊列中讀走消息。消息隊列是隨內核持續的

    消息隊列的內核持續性要求每個消息隊列都在系統范圍內對應唯一的鍵值,所以,要獲得一個消息隊列的描述字,只需提供該消息隊列的鍵值即可;

    消息隊列與管道以及有名管道相比,具有更大的靈活性,首先,它提供有格式位元組流,有利於減少開發人員的工作量;其次,消息具有類型,在實際應用中,可作為優先順序使用。這兩點是管道以及有名管道所不能比的。同樣,消息隊列可以在幾個進程間復用,而不管這幾個進程是否具有親緣關系,這一點與有名管道很相似;但消息隊列是隨內核持續的,與有名管道(隨進程持續)相比,生命力更強,應用空間更大。

    信號燈與其他進程間通信方式不大相同,它主要提供對進程間共享資源訪問控制機制。相當於內存中的標志,進程可以根據它判定是否能夠訪問某些共享資源,同時,進程也可以修改該標志。除了用於訪問控制外,還可用於進程同步。信號燈有以下兩種類型:

    int semop(int semid, struct sembuf *sops, unsigned nsops); semid是信號燈集ID,sops指向數組的每一個sembuf結構都刻畫一個在特定信號燈上的操作。

    int semctl(int semid,int semnum,int cmd,union semun arg)
    該系統調用實現對信號燈的各種控制操作,參數semid指定信號燈集,參數cmd指定具體的操作類型;參數semnum指定對哪個信號燈操作,只對幾個特殊的cmd操作有意義;arg用於設置或返回信號燈信息。

    進程間需要共享的數據被放在一個叫做IPC共享內存區域的地方,所有需要訪問該共享區域的進程都要把該共享區域映射到本進程的地址空間中去。系統V共享內存通過shmget獲得或創建一個IPC共享內存區域,並返回相應的標識符。內核在保證shmget獲得或創建一個共享內存區,初始化該共享內存區相應的shmid_kernel結構注同時,還將在特殊文件系統shm中,創建並打開一個同名文件,並在內存中建立起該文件的相應dentry及inode結構,新打開的文件不屬於任何一個進程(任何進程都可以訪問該共享內存區)。所有這一切都是系統調用shmget完成的。

    shmget()用來獲得共享內存區域的ID,如果不存在指定的共享區域就創建相應的區域。shmat()把共享內存區域映射到調用進程的地址空間中去,這樣,進程就可以方便地對共享區域進行訪問操作。shmdt()調用用來解除進程對共享內存區域的映射。shmctl實現對共享內存區域的控制操作。這里我們不對這些系統調用作具體的介紹,讀者可參考相應的手冊頁面,後面的範例中將給出它們的調用方法。

    註:shmget的內部實現包含了許多重要的系統V共享內存機制;shmat在把共享內存區域映射到進程空間時,並不真正改變進程的頁表。當進程第一次訪問內存映射區域訪問時,會因為沒有物理頁表的分配而導致一個缺頁異常,然後內核再根據相應的存儲管理機制為共享內存映射區域分配相應的頁表。

    閱讀全文

    與linux缺頁異常相關的資料

    熱點內容
    怎麼初步認識編程 瀏覽:206
    為什麼程序員都喜歡谷歌 瀏覽:889
    壓縮性骨拆能自愈嗎 瀏覽:275
    安卓怎麼設置游戲畫面 瀏覽:112
    k線上寫字源碼 瀏覽:455
    單擊按鈕保存資料源碼 瀏覽:352
    華為gt加密卡 瀏覽:211
    河北超融合伺服器廠家雲主機 瀏覽:892
    芙兒優安全座椅app怎麼連接 瀏覽:292
    專業美團騎手app怎麼開通 瀏覽:947
    個人音樂分享網站源碼 瀏覽:375
    在新電腦上怎麼注冊加密狗 瀏覽:123
    最後一戰游戲源碼 瀏覽:5
    phpmysql實例下載 瀏覽:751
    傳智黑馬安卓非加密 瀏覽:553
    伺服器如何配置host 瀏覽:1001
    守望執行命令 瀏覽:371
    加密狗插上去了怎麼辦 瀏覽:624
    錘子m1怎麼把文件夾重置 瀏覽:213
    APP的數據會存在哪裡 瀏覽:66