一、操作系統的概念
定義:從本質上來說操作系統就是把底層硬件抽象成了一層虛擬機,所以說計算機本身就是一個虛擬機。計算機本身並不會做任何事情,它就是一堆鐵疙瘩,即使給它加電它也不會做任何事情,cpu只有在程序的指揮下才會做事情。所以,操作系統的啟動就是一個自舉的過程,上電的一剎那間主板上的一塊ROM芯片中的代碼會被自動映射到內存的低地址空間,這塊ROM芯片中存的就是BIOS。
二、核心五大部件
在馮諾依曼體系中,計算機有五大部件,分別是運算器、控制器、寄存器、輸入設備和輸出設備。其中CPU最核心的是運算器、控制器和寄存器。
運算器:負責算術、邏輯運算等,
控制器:控制指令,包括數據的存取過程。程序就是由指令+數據組成的
寄存器(Register):將取回的數據暫存於此,同時起到中間數據計算結果存放的功能。
因此運算器在控制器的控制下不斷從寄存器中讀取數據處理,在計算機內部的存儲器速度是最快的,稱為寄存器(暫存)。之所以稱為寄存器是因為里面的數據刷新頻率很快可以跟CPU刷新頻率同步,接下來比較快的是一級緩存、二級緩存、三級緩存,再到外面就是內存了,從內到外造價越來越低,存取速度越來越慢,容量越來越大。一級緩存又被分為一級指令緩存,一級數據緩存;二級緩存就沒有這樣區分了。多核CPU,每一核CPU都有自己一級、二級緩存,而三級緩存是共享的。
寄存器固然能存儲數據,但是空間太小,不是存儲設備的核心部件,因此必須要跟內存設備打交道
內存(RAM):由多個存儲單元組成,一個字節為一個存儲單元,或者一個cell為一個存儲單元。每個cell都有自己的存儲地址,以16進制進行編制。
CPU要想存取數據,就需要知道內存中數據的存儲地址,必須要具備尋址數據功能。
北橋芯片(NorthBridge):用來處理高速信號。通常處理CPU(處理器)、RAM(內存)、AGP端口或PCI Express和南橋芯片之間的通信。即是CPU電路單元和RAM的存儲電路建立關聯關系
32位CPU:相當於CPU有32根地址線與內存相連,每根地址線都能傳輸0和1兩個位的信號,即能處理的信息為2^32bits=512Mbyte,32根線總共會決定出2^32次方個位置,每一個位置都是1Byte,這是內存的基本單位,因此最多能支持的內存為2^32Byte=4GB。同理64位CPU,4G*4G相當於40多億個4G。
CPU完成尋址的線路、數據讀取的線路、控制指令線路,如果每個線路都配上32根地址線就變得相當復雜,因此CPU將這些線路進行復用(線路復用),通過控制位來區分哪些是數據讀取、哪些上尋址等
PAE(物理地址擴展):Physical Address Extension。在32位(bit)尋址總線的基礎上再增加4bit相當於2^36Byte=64G,但是需要操作系統內核支持尋址64G的能力。32位操作系統無論內核是否支持PAE,單個進程的所能使用的地址空間超不過3G,剩下1G映射給內核了。例如MySQL就是單進程多線程運行,在32位操作系統上最多能使用的內存只有2.7G,因此最好使用64位的操作系統安裝64位的MySQL。
這里牽扯到一個問題,為什么緩存可以提高速度?是因為程序的局部性原理,就是我們常說的二八法則。一段程序中執行頻率最高的代碼往往只有20%,80%的代碼很少用到,而這20%的代碼完成了整個程序80%的功能。我們可以將這20%的代碼緩存到cpu一級緩存或者二級緩存中,因為cpu中的cache的速度和cpu的時鍾頻率最接近,這樣就可以提高程序運行的速度,
一般緩存算法設計的思路都是:最近最少使用原則。將最近最少使用的數據從緩存中移除,畢竟緩存的空間是有限的,要是緩存和內存的價格一樣,那就沒必要設計緩存了。程序的局部性原理分為空間局部性和時間局部性:空間局部性是指一段代碼被訪問了,那么它周圍的代碼也極有可能被訪問到。時間局部性是指某一時刻一段代碼被訪問了,那么過一會兒這段代碼極有可能被再次訪問。
N路關聯:PC機一級緩存空間通常為64KB,而內存遠遠大於一級緩存。但是CPU讀取數據一定是來自一級緩存的,一級緩存沒有就去二級緩存尋找,找到即替換一級緩存中的,反之一層一層往下找,直到內存中。由於一級緩存與內存差異巨大,CPU命中需要數據的概率極低。RAM中的每個存儲單元都可以直接緩存在一級緩存中的每一個位置,這叫直接映射,因此為了提高命中率引入了N路關聯技術。原理上將RAM分為幾片,同理一級緩存也分為對應的幾片,如下圖,所謂1路關聯(1 way),內存中的00、08、16、24只能緩存在set 0單元上,01、09、17、25只能緩存在set1單元上,如果00已經在set0單元上,此時08想緩存在set0上,就要將00置換掉
2路關聯
如下圖,00、08可以同時緩存在set0單元上,而16、24想緩存時需要置換前兩個
4路關聯
如下圖,00、08、16、24可以同時緩存在set0單元上,而01、09、17、25想緩存時需要置換前四個
完全關聯
通寫策略(write-through):在數據更新時,同時寫入緩存Cache和后端存儲。此模式的優點是操作簡單;缺點是因為數據修改需要同時寫入存儲,數據寫入速度較慢。
回寫策略(Write-back):在數據更新時只寫入緩存Cache。只在數據被替換出緩存時,被修改的緩存數據才會被寫到后端存儲。此模式的優點是數據寫入速度快,因為不需要寫存儲;缺點是一旦更新后的數據未被寫入存儲時出現系統掉電的情況,數據將無法找回。
顯卡(video card):跟CPU數據交互量上非常大的,也是接在北橋上的,是一個高速總線。
IO設備:除了CPU中的運算器、控制器、寄存器之外的設備都是IO設備,IO設備分為低速IO和高速IO。高速IO通常指的的PCI總線
為了銜接計算機系統中各個速度比cpu慢的設備,早期的主板上集成有北橋芯片和南橋芯片(現在的主板可能已經不是這么設計了),南橋芯片是將各慢速設備匯總起來一起接入北橋芯片,所以橋接芯片說白了就是匯總各外部設備最終完成和cpu的交互。south bridge上接的通常稱為ISA總線,早期的PCI總線都是接到南橋上的,接入北橋的稱為PCI-E,PCI-E總線的速度比PCI總線的速度要快得多得多。常見的磁盤總線都是PCI格式的,SCSI、IDE、SATA統稱為PCI總線,PCI(外部設備互連)只是一種統稱。鼠標、鍵盤是串行接口的,通常U盤是通過PCI總線連接南橋-北橋-CPU進行數據交互的。如果把U盤做成PCI-E接口,線路帶寬足夠大,而U盤太慢了,此時把N個U盤並行連接當作一個存儲盤使用,由一根PCI-E總線連接北橋與CPU進行數據交互,這種稱為固態硬盤。現在很多固態硬盤接口都上SATA接口,建議購買PCI-E接口的固態硬盤。那么問題來了,計算機接了這么多的外部設備,cpu如何區分不同的IO設備呢?類比計算機區分和互聯網通信的各個進程的方法,計算機區分不同的和外部通信的進程靠的是套接字,也就是ip地址+端口號。這里cpu區分不同IO設備靠的也是端口號,稱為IO端口,在一台計算機上IO端口的數目也是65535個。任何一個硬件設備通過IO總線接入計算機的時,它必須一開機就申請注冊一批連續的IO端口。
任何一個硬件設備的電路可能跟CPU內部電路不一致,因此每一個外部設備都有控制器、適配器,控制器和適配器是將外部設備的信號轉換成連接CPU總線上能理解的信號,相當於翻譯官,同時控制外部設備的傳輸速率、校驗等功能。所謂驅動就是指揮控制器芯片與硬件工作的。
輪巡(poll):CPU連接這么多外接設備,是如何區分電信號是來自硬盤、鼠標還是網卡,它每隔幾毫秒去輪巡一次,查看這些設備有沒有信號傳輸。
中斷(interrupt):因poll效率非常低,因此每個設備發送信號時通知CPU來查看,CPU怎么得知是哪個設備的信號呢?可能你會想到通過IO端口來識別,IO端口是實現數據交互而不是識別信號交互的
中斷控制器(Interrupt Controller):CPU外置芯片,接收中斷信號。當某個外部設備(例如網卡卡)傳來信號,CPU中斷當前操作,將此信號接收至內存中。中斷控制器上連接着中斷線,每根線代表一個設備(不是固定的設備),用來區分外部設備,線路是可以復用的,
直接內存存取(DMA):如果CPU需要處理每個外部設備發來的信號,將會使CPU很繁瑣,因此引用DMA來解決這個問題。由CPU在內存中划好某次傳輸數據所需空間,並授權某根線路給DMA使用。它允許不同速度的硬件裝置來溝通,而不需要依賴於 CPU 的大量中斷負載。否則,CPU 需要從來源把每一片段的資料復制到暫存器,然后把它們再次寫回到新的地方。在這個時間中,CPU 對於其他的工作來說就無法使用。
在實現DMA傳輸時,是由DMA控制器直接掌管總線,因此,存在着一個總線控制權轉移問題。即DMA傳輸前,CPU要把總線控制權交給DMA控制器,而在結束DMA傳輸后,DMA控制器應立即把總線控制權再交回給CPU。一個完整的DMA傳輸過程必須經過DMA請求、DMA響應、DMA傳輸、DMA結束4個步驟。
在物理內存當中,最低地址那段空間,最容易尋址的那段空間的起始地址就預留給DMA,一般16M,在DMA最前面還有1M使用的空間是留給BIOS。
CPU工作頻率比較快,內存工作頻率比較慢,當內存傳輸數據給CPU時,CPU大部分時間上處於空閑狀態的,因此CPU與慢設備打交道時,會浪費許多時鍾周期。CPU內部有一個時間產生器(晶體振盪器),始終產生着時鍾脈沖。如下圖CPU已經轉了好幾個周期才開始與內村打交道。CPU為了協調步伐一致,要規划好多少個周期后與內存交互。一般上在時鍾周期的上升沿(即高電平與低電平進行切換時)進行交互。
操作系統:CPU與外部設備交互經常步伐不一致,為了合理利用CPU資源,即衍生出了Monitor(監控器),發展到后來就成為OS(操作系統),再后來操作系統把計算機抽象成虛擬機。
之所以把操作系統稱為虛擬機,是因為我們只有一塊cpu芯片(可能是多核心的),只有一塊內存,鼠標只有一個,鍵盤只有一個........但是每個進程都想獨占這一整套資源。cpu通過時間片輪轉的方式將一個cpu芯片虛擬成多個cpu進行,內存的虛擬通過分頁機制,將內存切割成一個個固定大小的頁面。好了,現在已經把計算機系統中最重要的兩個部件運算器和存儲器虛擬出來了(其實就是虛擬的cpu和內存),剩下的那些IO設備如何虛擬呢?其實在IO虛擬不需要專門去做,因為當前哪個進程獲得了系統使用權,IO設備就交給整個進程。
進程:一個程序有許多功能,但加載程序的部分功能到CPU中執行的實例稱為進程,相當於一個獨立運行單位。
多個獨立進程同時運行,CPU、緩存、內存、IO設備上如何合理的分配資源的?
1、 CPU:將時間切割成各個獨立單位,在時間的維度完成切片進而完成CPU虛擬。
2、 緩存:有足夠空間可用即不需要做什么,如果沒有的話,則要進行保存。但是這個進程還沒有執行完,CPU分配的時間已經到下一個進程了,此時就要保存現在這個進程的指令數(CPU內部的指令計數器:寄存器),也就是保存現場,再次回來就要恢復現場
3、 內存:將空間切片,內核預留一部分,依次分給進程1、進程2等等以此類推,如果是這樣划分的話,進程隨時啟動,隨時終止,或者有的進程需要的空間大,有的小,划分的空間進程不夠用,有的划分得太多,所以得引入內存保護機制。如此只能將內存按固定大小進行切割,例如按4k大小為一個單位(存儲槽)進行切割,每個存儲槽為一個頁框(page frame),每個存儲槽存儲的數據叫一個頁面(page),在頁面和頁框上加一個頁和頁框的映射機制,這個映射上面的進程都各自認為自己擁有所有內存。進程空間(指令區+代碼區+數據區+bss段+堆+棧)如,指令一個頁,代碼一個頁,數據兩個頁等,如下圖,代碼區和棧由控制芯片映射到內存中的某個頁框上,並不是連續的頁框。站在進程的角度來講,它所需數據的地址是線性地址,而真正數據上存放在物理地址,因此需要通控制芯片進行查找,如此多的數據,控制芯片是如何快速查找的呢?其實在控制芯片里將兩者的對應關系划分成了頁目錄(一級目錄、二級目錄、三級目錄等)
4、I/O設備:要跟硬件(即I/O設備)打交道必須通過內核,由內核轉給進程。
cpu芯片只有一塊,在某一時刻,要么是內核進程在上面運行,要么是用戶空間進程在上面運行,內核在cpu上運行時稱為內核模式,進程在cpu上運行時稱為用戶模式。而在內存中內核占據的那段內存空間稱為內核空間,用戶進程占據的空間叫用戶空間。用戶模式時,進程是不能直接控制硬件的。這是因為在cpu內部,cpu制造商將cpu能運行的指令划分為4層(僅對x86架構而言),ring0,ring1,ring2,ring3,由於歷史原因,ring1和ring2並沒有使用,linux只用了ring0和ring3。ring0稱為內核模式,也稱為特權指令模式,可以直接操控硬件,ring3是用戶模式,可以執行一般指令。
當一個運行中的進程要打開文件或者操作麥克風,它發現自己沒有權限執行特權指令,於是就會發起系統調用,一旦產生系統調用進程就會退出,從用戶模式切換到特權模式,稱為模式切換。由內核負責將數據裝載至物理內存(物理內存為內核與各進程用戶都划分了一段自己的空間)先到內核空間中,再轉至進程用戶空間,然后映射到線性地址上,這個時候內核再喚醒用戶進程進行數據交互。如果有多個進程,即進程隊列,這里就牽扯到進程的狀態了,這里簡單介紹幾個。就緒狀態:就緒是指在所有進程隊列中,這個進程所需的所有資源都已經准備好了。沒有就緒的稱為睡眠狀態,而睡眠狀態又分為可中斷睡眠和不可中斷睡眠,區別是:可中斷睡眠是指隨時可以喚醒的,不可中斷睡眠是指內核為它准備的數據還沒有准備好,即使喚醒它,它也不能干活。可中斷睡眠不是因為資源沒有准備好而睡眠,只是一個階段的活已經干完了,下一階段的活兒還沒來,於是它就去睡覺了,你可以隨時叫醒它,所以稱可中斷睡眠。而不可中斷睡眠一般是因為IO而進入睡眠狀態的。
進程結構:進程是通過雙向鏈表(List)來管理的,而這個鏈表是有次序的。如下圖:通過其中一個可以找到下一個,每個進程在Linux內核內部靠一個獨立的數據結構來管理,這個結構叫Task_structure(C語言描述獨立數據組織的數據結構),而這個整體文件也被稱作進程描述符(類似文件的元數據)。每個進程都有進程描述符,在創建一個進程時,首先創建一個進程描述符,並添加到雙向鏈表上,刪除則在此表上刪除描述符。
從內核觀點看,創建一個進程除了要給進程分配系統資源(CPU 時間、內存等),還要在內核的內存空間中給它維護一個進程描述符文件。這個文件保存着當前進程的所有相關信息,這個文件結構如下:
進程切換(Context switch):又稱為上下文切換。如下圖A進程和B進程進行切換。
假如,B進程替換A進程,A的進程描述符(task_struct)要被CPU掛起(suspend),意味着在CPU上的占位符(stack pointer)、其他寄存器(other registers)、指令計數器(EIP register)等等所維持的數據都需要保存到A進程描述符文件當中(稱為保存現場),而進程描述符是由內核維護着,描述符大小是固定的。當A進程被掛起,B進程就被執行進來,這個過程叫恢復現場(resume)。可以看出上下文切換也是需要花時間的,而且上下文切換是由內核來完成,意味着每一次進程切換都要從用戶模式轉到內核模式,然后再到用戶模式如此這樣運行下去,不可能一個進程直接到另一個進程,必須由內核完成,則整體時間被分為兩部分(用戶模式所占據的時間(%us)+內核模式所占據的時間)。所有內核占據的這部分時間就是下圖的%sy所占據時間
通常內核模式是不應該占據太多時間的,如果占用時間過多大都是進程切換,中斷次數等過多導致的,而進程大部分時間是跟用戶模式打交道的。
進程搶占:根據時鍾中斷搶占,也就是系統時鍾(內部時鍾頻率),稱為嘀嗒(tick)。
注:Linux有系統時鍾和硬件時鍾。
Tick每秒嘀嗒的次數稱為時間解析度,中斷次數Hz
100Hz:一秒嘀嗒100次,每秒100次時間中斷
1000Hz:一秒嘀嗒1000次,1ms嘀嗒一次
通常每次滴答都會產生可搶的時鍾中斷
調度器:其任務是在程序之間共享CPU時間, 創造並行執行的錯覺, 該任務分為兩個不同的部分, 其中一個涉及調度策略, 另外一個涉及上下文切換。內核必須提供一種方法, 在各個進程之間盡可能公平地共享CPU時間, 而同時又要考慮不同的任務優先級.
調度器的一個重要目標是有效地分配 CPU 時間片,同時提供很好的用戶體驗。調度器還需要面對一些互相沖突的目標,例如既要為關鍵實時任務最小化響應時間, 又要最大限度地提高 CPU 的總體利用率.
調度器的一般原理是, 按所需分配的計算能力, 向系統中每個進程提供最大的公正性, 或者從另外一個角度上說, 他試圖確保沒有進程被虧待.
進程分類:
交互式進程(I/O):例如編輯器,大量時間等待IO上與CPU打交道比較少
批處理進程(CPU):守護進程,大量CPU時間
實時進程(Real-time):立即響應,優先級最高
類型 |
描述 |
示例 |
交互式進程(interactive process)
|
此類進程經常與用戶進行交互, 因此需要花費很多時間等待鍵盤和鼠標操作. 當接受了用戶的輸入后, 進程必須很快被喚醒, 否則用戶會感覺系統反應遲鈍 |
shell, 文本編輯程序和圖形應用程序 |
批處理進程(batch process) |
此類進程不必與用戶交互, 因此經常在后台運行. 因為這樣的進程不必很快相應, 因此常受到調度程序的怠慢 |
程序語言的編譯程序, 數據庫搜索引擎以及科學計算 |
實時進程(real-time process) |
這些進程由很強的調度需要, 這樣的進程絕不會被低優先級的進程阻塞. 並且他們的響應時間要盡可能的短 |
視頻音頻應用程序, 機器人控制程序以及從物理傳感器上收集數據的程序 |
如何設定是批處理進程優先級高呢還是交互式進程高呢?如果批處理進程優先級高,對於交互式進程的應用,例如編輯器輸入一個字母或者數字半天沒有響應;如果交互式進程優先級高,對於CPU來說大部分時間是空閑的,所以很浪費資源。因此調度器引用了以下分配策略:
CPU密集型(批處理進程):時間片長,優先級低
IO密集型(交互式進程):時間片短,優先級高
進程優先級(priority)定義:1、實時優先級;2、靜態優先級。優先級范圍從1~139
實時優先級:用1~99表示,數字越小,優先級越低
靜態優先級:用100~139表示,數字越小,優先級越高
實時優先級比靜態優先級高,通常內核進程都是實時優先級。優先級為RT就是實時優先級(1~99),而20是從100之后的20(即120),原因它的nice值是為0,這是站在用戶模式是講。如下圖:
查看進程優先級:ps -e -o class,rtprio,nice,pri,nice,cmd用這個命令可以查看優先級, class:表示調度類別
rtprio:實時優先級
nice:調整靜態優先級。nice值是-20到19,對應的是100到139,nice值為0對 應的為120,默認為0
pri:非實時優先級(靜態優先級)
cmd:執行的命令
如上圖,nice值是0,而pri為19,為什么不是20呢?原因是經過動態優先級調整(見動態優先級公式)。凡是帶有中括號的都是內核線程。
[root@localhost ~]# ps -e -o class,rtprio,nice,pri,nice,cmd
CLS RTPRIO NI PRI NI CMD
TS (other) - 0 19 0 /sbin/init
TS - 0 19 0 [kthreadd] 凡是加了括號的都為內核線程
FF (fifo) 99 - 139 - [migration/0]
TS - 0 19 0 [ksoftirqd/0]
FF 99 - 139 - [migration/0]
進程調度類別:
對於實時進程來講:
SCHED FIFO:first in first out(先進先出隊列)調度fifo類別的實時進程
SCHED_RR:round robin(輪調)調度rr類別的實時進程
SCHED_Other:專門用來調度用戶空間進程的,100-139之間的進程
Linux(紅帽)特有的兩個調度類別
SCHED_BATCH 調度批處理進程
SCHED_IDLE 調度空閑進程
動態優先級:如果110、115、120、130級進程都分別很多個,那處於末級的一直分配不到CPU資源,因此采用動態優先級策略。內核監控這些進程,如果某些進程長時間沒有分配到資源,內核將臨時性的調高優先級,主要對100-139的進程即SCHED_Other類進程,對用戶空間進程,長時間沒有獲得時間片或長時間獲得時間片,進行調高或者調低。
dynamic priority(動態優先級)= max (100,min ( static priority - bonus + 5,139))
動態優先級等於從100和 min ( static priority - bonus + 5,139)中取最大數
bonus(范圍0到10)
比如110的優先級要對它臨時性的調低,降低3級的措施后
110-(-3)+5 =118,118與139比取118,然后再與100相比,去最大數118。所以優先級被調整到118
手動調整進程優先級:
100-139進程:使用nice命令
nice N COMMAND
renice -n # PID 指定新的nice值並指定PID號
1-99進程:使用chrt命令
chrt:
-p 指定PID
-f 指定fifo類別
-r 指定 rr類別
-b 指定批處理進程
-i 空閑進程
chrt -f -p [prio] PID 調整fifo的優先級
chrt -r -p [prio] PID 調整rr的優先級
chrt -f -p [prio] COMMAND 啟動進程時直接指定實時優先級
ps -e -o class,rtprio,nice,pri,nice,cmd 查看命令
chrt -p [prio] COMMAND 也能調整100-139進程的優先級
內核調度是取優先級高的進程,通常從進程隊列中獲取。如何公平、快速的從數百個進程隊列中調取呢?
在現實生活中如何獲取公平:1、保證起點公平(一生下來,針對自己是什么都沒有,憑借自己的能力獲得經濟收入);2、二次分配(有些能力比較強高收入人交稅就多交點,分配給低收入人)。因此在計算機中公平的調取進程是需要融合多種策略來進行。
Linux內核2.6的調度算法,按照級別將所有的進程分成了139*2個隊列,優先級最高的是99,從優先級高到低每一次掃描隊列的首部,接着就是這列的第二個,每個隊列是包含2個(活動隊列、過期隊列),如果一個進程當它的時間片到了,進程沒有執行完,就將其放入過期隊列,等下次時間片。所以無論有多少個進程,隊列一個只有139個,因此每次一共只要掃描139,這就是O(1)算法
一開始的調度器是復雜度為O(n)O(n)的始調度算法(實際上每次會遍歷所有任務,所以復雜度為O(n)), 這個算法的缺點是當內核中有很多任務時,調度器本身就會耗費不少時間,所以,從linux2.5開始引入赫赫有名的O(1)調度器
然而,linux是集全球很多程序員的聰明才智而發展起來的超級內核,沒有最好,只有更好,在O(1)調度器風光了沒幾天就又被另一個更優秀的調度器取代了,它就是CFS調度器Completely Fair Scheduler。這個也是在2.6內核中引入的,具體為2.6.23,即從此版本開始,內核使用CFS作為它的默認調度器,O(1)調度器被拋棄了, 其實CFS的發展也是經歷了很多階段,最早期的樓梯算法(SD), 后來逐步對SD算法進行改進出RSDL(Rotating Staircase Deadline Scheduler), 這個算法已經是”完全公平”的雛形了, 直至CFS是最終被內核采納的調度器, 它從RSDL/SD中吸取了完全公平的思想,不再跟蹤進程的睡眠時間,也不再企圖區分交互式進程。它將所有的進程都統一對待,這就是公平的含義。CFS的算法和實現都相當簡單,眾多的測試表明其性能也非常優越。
字段 |
版本 |
O(n)的始調度算法 |
linux-0.11~2.4 |
O(1)調度器 |
linux-2.5 |
CFS調度器 |
linux-2.6~至今 |
進程空間:進程空間由text、data、bss、heap、stack組成,如下圖:
程序段(Text):程序代碼在內存中的映射,存放函數體的二進制代碼,只讀段的指令區。
初始化過的數據(Data):在程序運行初已經對變量進行初始化的數據。
未初始化過的數據(BSS):在程序運行初未對變量進行初始化的數據(即初始化為0的變量)。
堆 (Heap):存儲動態內存分配,需要程序員手工分配,手工釋放.注意它與數據結構中的堆是兩回事,分配方式類似於鏈表。比如打開文件通常都在堆上。
棧 (Stack):存儲局部、臨時變量,函數調用時,存儲函數的返回指針,用於控制函數的調用和返回。在程序塊開始時自動分配內存,結束時自動釋放內存,其操作方式類似於數據結構中的棧。例如程序運行過程產生的變量、返回的值都保存在棧上。
進程的創建:Linux的每個進程都是由父進程生成,第一個進程是init(有kernel來創建的),而后其他的所有進程都是由init來生成,或者init的子進程生成。通過fork()來創建,其實就是通過系統調用。創建一個子進程最重要的是創建它的描述符(task_struct),這個是由內核完成的,創建完成之后是需要分配內存空間的,剛開始創建,內存空間是和父進程共享的,cow(copy on write)即父進程、子進程需要修改內容的時候就分配單獨空間。
三、CPU負載觀察及調優方法
RHEL 6.4已經實現無嘀嗒效果(tick less)即時鍾中斷。按100Hz:一秒嘀嗒100次,每秒100次時間中斷,就算CPU在空閑時也要進行時間中斷,是很消耗電源或者資源的,更重要的是大量時間CPU都在空載。因此在這種場景下,紅帽6以后的內核采用了無嘀嗒的機制,CPU可以進行深度睡眠,完全靠中斷驅動(interrupt-driven)。其包括軟中斷和硬中斷。
軟中斷:由軟件產生的中斷,系統調用需要從用戶模式切換到內核模式稱為軟中斷。
硬中斷:由硬件產生的中斷稱為硬中斷
現在CPU都是多核的,而在服務器領域使用多顆CPU是正常的。在SMP(對稱多處理器:一塊主板上有多個CPU插槽)多CPU架構中,每個插槽稱為一個socket,當多個CPU訪問同一個內存,傳統上多CPU對於內存的訪問是總線方式。是總線就會存在資源爭用和臨界區問題,而且如果不斷的增加CPU數量,總線的爭用會愈演愈烈,這就體現在4核CPU的跑分性能達不到2核CPU的2倍,甚至1.5倍都沒有。理論上來說這種方式實現12core以上的CPU已經沒有太大的意義。
為了防止多顆CPU訪問內存出現資源爭用,可以采取多核CPU共享三級緩存的策略,因為三級緩存是在CPU內部,速度比訪問內存快就算由資源爭用也會影響比較小,即為每個CPU分布一個專用內存並且配有專用控制器。這種情況下,由於內存已經屬於系統級別,內核加載時有可能把數據加載到不同CPU的專用內存上,則CPU調度隊列就不在是一個隊列,每個CPU都有自己的進程隊列,但是這些隊列會不斷被內核進行平衡(rebalancing),資源平均利用,這樣就有可能會導致1號CPU需要到2號CPU的專用內存上讀寫數據,這種現象稱為非一致性內存訪問(NUMA)。如下圖,CPU 0-3訪問自己的內存需要1、2、3步驟(3個時鍾周期),而訪問CPU 4-7的內存需要1、1a、2、3步驟,其中1a就需要消耗3個時鍾周期。
在企業中,NUMA結構是很常見的,為了避免內存間交叉訪問,性能下降,禁止內核進行平衡,對於比較繁忙的、經常性批處理的服務進程可以采取CPU綁定(CPU affinity)策略即CPU姻親關系。將左邊灰色也就CPU 0-3那一排稱為一個節點(node),另一排稱為另一個節點
Intel的NUMA解決方案,放棄總線的訪問方式,將CPU划分到多個Node中,每個node有自己獨立的內存空間。各個node之間通過高速互聯通訊,通訊通道被成為QuickPath Interconnect即QPI。
NUMA一共有4個命令:
numactl控制命令,實現策略控制
-cpunodebind=nodes 將cpu跟某個node綁定,不讓cpu訪問其他node
-physcpubind=cpus 將進程和cpu完成綁定
--show顯示當前使用的策略
numastat 顯示幾個node的命令
-p:查看某個特定進程的內存分配,如果某個內存分配跨越好幾個node則需
要進行CPU綁定
-s node0:查看node0和全部的,主要用於排序
[root@localhost ~]# numastat 顯示有幾個node
node0
numa_hit 283715 #表示cpu到這個內存node(節點)找數據,找到即命中多少個
numa_miss 0 #沒命中個數,命中過高需要綁定進程到特定cpu
numa_foreign 0 #被非本地cpu訪問次數
interleave_hit 14317
local_node 283715
other_node 0
當numa_miss值過高時就需要進行CPU綁定進程
numad:用戶空間級別的守護進程,能夠提供策略,通過觀察cpu每個進程的狀
況,自動把某個進程綁定到特定的cpu上,實現自我監控,自我優化,自我管理。
numad是在硬件級別將某個進程跟cpu和node綁定。如果真想建立CPU的姻親關系即將CPU和進程進行綁定,有專門的工具,就算是非NUMA架構也可以實現,專門將某個進程跟CPU綁定,也有專門的命令taskset
taskset:綁定進程至某cpu上。是以mask掩碼的方式表現的:例如0x0000 0001(16進制)0001:表示第0號CPU
0x0000 0003
0003換成二進制0011: 表示第0號和1號CPU
0x0000 0005
0005換成二進制0101:表示第0號和2號CPU
0x0000 0007
0007換成二進制0111:表示第0、1、2號CPU
列如把pid為101的進程綁定在3號cpu上
Taskset的用法:將pid101綁定在1號CPU
#taskset -p 掩碼 pid
例如:1號CPU為0010換算成十進制為0002
[root@localhost ~]# taskset -p 0x0000 0002
或者加參數-c,直接指定第幾號CPU
[root@localhost ~]# taskset -p -c 1 101
綁定在0號和1號cpu上
[root@localhost ~]# taskset -p -c 0,1 101
綁定在0-2號和7號cpu上
[root@localhost ~]# taskset -p -c 0-2,7 101
如果一個CPU有16核,0和1核用來運行其他進程和系統中斷,2-15用來運行某個進程並且隔離系統中斷。
[root@localhost ~]# cat /proc/irq/0/smp_affinity
ffffffff,ffffffff,ffffffff,ffffffff #表示0號中斷可以運行到任何cpu上,全部f表示任何CPU
應該將中斷綁定至那些非隔離的CPU上,從而避免那些隔離的cpu處理中斷程序
# echo "00000000,00000000,00000000,0000001" > /proc/irq/0/smp_affinity
將0號中斷綁定到 0號cpu上
echo CPU_MASK >/proc/irq/<irq number>/smp_affinity
NUMA模式可以通過當numa_miss值過高時就需要進行CPU綁定進程,那非NUMA模式如何確定什么時候需要綁定中斷?什么時候需要綁定到CPU上。通過以下命令進行查看運行狀態(sar -q、top、w、uptime、vmstat 1 5)。
[root@localhost ~]# sar -q 1 #每隔1秒實時監控隊列長度負載平均值
09:32:17 AM runq-sz plist-sz ldavg-1 ldavg-5 ldavg-15
運行隊列長度
09:32:18 AM 0 96 0.00 0.00 0.00
09:32:19 AM 0 96 0.00 0.00 0.00
通常還可以對web服務器進行ab壓力測試,以上數值會進行急劇增長。ab是apachebench命令的縮寫。
-n在測試會話中所執行的請求個數。默認時,僅執行一個請求。
-c一次產生的請求個數。默認是一次一個。
#10000個並發數為300的請求數
[root@localhost ~]# ab -n 10000 -c 300 http://127.0.0.1/index.php
Runq-sz為運行隊列長度,3、6、20代表有多少個進程在等待運行調度,如果只有一顆CPU的時候,隊列長期超出3,說明要升級了。
查看CPU狀態命令(mpstat命令)
mpstat 1 2 顯示每一顆cpu的平均使用率,-P指定查看哪顆CPU
[root@localhost ~]# mpstat -P 0 1 #只顯示0號cpu,每秒鍾顯示一次
%usr用戶空間的, %sys內核空間的,%iowait IO等待的,%irq硬處理中斷的,%soft軟中斷的,%steal被虛擬機偷走的,%guest虛擬機使用的,%idle空閑的
09:35:33 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
09:35:34 AM 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
09:35:35 AM 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
09:35:36 AM 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
[root@localhost ~]# mpstat -I CPU 1 #顯示cpu對中斷處理的
sar命令
[root@localhost ~]# sar -P 0 1 #顯示0號CPU使用情況(每秒顯示一次)
[root@localhost ~]# sar -w 1 #每秒鍾上下文切換的次數以及進程創建的個數
vmstat命令也可以查看上下文切換的次數,如下圖中cs列:
iostat命令
[root@localhost ~]# iostat -c 1 2 #一共采樣2次,每隔一秒采樣一次
/proc/stat文件
[root@localhost ~]# cat /proc/stat
dstat命令
dstat --top-cpu 查找誰最消耗cpu
--top-cputime查找誰消耗cpu時間最長的
--top-io誰消耗IO最多
--top-latency哪個進程是最大延遲
--top-mem 誰用了最多的內存
CPU調度域(Scheduler domains)
CPU的調度結構類似於文件系統,把CPU組織成倒置的樹狀結構,所有CPU都屬於根域。例如有4顆CPU,分別是0、1、2、3、4CPU,將根域划分為2個子域(子域A和子域B),CPU0和2綁定在子域A,CPU1和3綁定在子域B。如果將進程綁定在根域上,將運行在所有CPU上;如果將進程綁定在子域A上,進程將運行在CPU0和2上。在划分CPU調度域時,不光要將CPU划分其中,內存也要將划分在域內,如果是NUMA結構的,將node划分其中即可;如果是非NUMA結構,每個CPU都要帶上自己那段內存。
CPU調度域的划分
1、創建一個子目錄cpusets
2、編輯/etc/fstab,將文件系統設備cpuset掛載到/cpusets目錄下,文件系統類型也叫cpuset
3、掛載完之后自動生成/cpusets/cpus、/cpusets/mems、/cpusets/tasks這幾個目錄
[root@localhost ~]# mkdir /cpusets
[root@localhost ~]# vim /etc/fstab
cpuset /cpusets cpuset defaults 0 0
[root@localhost ~]# mount -a #掛載命令執行一下
[root@localhost ~]# mount #查看一下是否有掛載
[root@localhost ~]# ls -l /cpusets/
total 0
--w--w--w-. 1 root root 0 Jun 5 10:39 cgroup .event_control
-rw-r--r--. 1 root root 0 Jun 5 10:39 cgroup.procs
-rw-r--r--. 1 root root 0 Jun 5 10:39 cpu_exclusive
-rw-r--r--. 1 root root 0 Jun 5 10:39 cpus
-rw-r--r--. 1 root root 0 Jun 5 10:39 mem_exclusive
-rw-r--r--. 1 root root 0 Jun 5 10:39 mem_hardwall
-rw-r--r--. 1 root root 0 Jun 5 10:39 memory_migrate
-r--r--r--. 1 root root 0 Jun 5 10:39 memory_pressure
-rw-r--r--. 1 root root 0 Jun 5 10:39 memory_pressure_enabled
-rw-r--r--. 1 root root 0 Jun 5 10:39 memory_spread_page
-rw-r--r--. 1 root root 0 Jun 5 10:39 memory_spread_slab
-rw-r--r--. 1 root root 0 Jun 5 10:39 mems
-rw-r--r--. 1 root root 0 Jun 5 10:39 notify_on_release
-rw-r--r--. 1 root root 0 Jun 5 10:39 release_agent
-rw-r--r--. 1 root root 0 Jun 5 10:39 sched_load_balance
-rw-r--r--. 1 root root 0 Jun 5 10:39 sched_relax_domain_level
-rw-r--r--. 1 root root 0 Jun 5 10:39 tasks
[root@localhost cpusets]# cat cpus #cpu查看的根域里包含的cpu有哪些
0
[root@localhost cpusets]# cat mems #查看內存根域有多少段
0
[root@localhost cpusets]# cat tasks #查看運行在根域的進程有哪些
創建cpu的子域
進入/cpusets/目錄中
[root@localhost cpusets]# mkdir domain1
[root@localhost cpusets]# cd domain1/
自動創建了以下文件
[root@localhost domain1]# ls -l
total 0
--w--w--w-. 1 root root 0 Jun 5 10:46 cgroup.event_control
-rw-r--r--. 1 root root 0 Jun 5 10:46 cgroup.procs
-rw-r--r--. 1 root root 0 Jun 5 10:46 cpu_exclusive
-rw-r--r--. 1 root root 0 Jun 5 10:46 cpus
-rw-r--r--. 1 root root 0 Jun 5 10:46 mem_exclusive
-rw-r--r--. 1 root root 0 Jun 5 10:46 mem_hardwall
-rw-r--r--. 1 root root 0 Jun 5 10:46 memory_migrate
-r--r--r--. 1 root root 0 Jun 5 10:46 memory_pressure
-rw-r--r--. 1 root root 0 Jun 5 10:46 memory_spread_page
-rw-r--r--. 1 root root 0 Jun 5 10:46 memory_spread_slab
-rw-r--r--. 1 root root 0 Jun 5 10:46 mems
-rw-r--r--. 1 root root 0 Jun 5 10:46 notify_on_release
-rw-r--r--. 1 root root 0 Jun 5 10:46 sched_load_balance
-rw-r--r--. 1 root root 0 Jun 5 10:46 sched_relax_domain_level
-rw-r--r--. 1 root root 0 Jun 5 10:46 tasks
開始綁定第0顆cpu到子域上
[root@localhost domain1]# echo 0 > cpus
[root@localhost domain1]# cat cpus
0
綁定第0段內存到子域上
[root@localhost domain1]# echo 0 > mems
[root@localhost domain1]# cat mems
0
將某個進程綁定到子域上,這個進程就只能在子域的cpu和內存段運行
[root@localhost domain1]# ps axo pid,cmd
[root@localhost domain1]# echo 16380 > tasks #例如將pid為16380的httpd進程綁定到子域上
[root@localhost domain1]# ps -e -o psr,pid,cmd | grep httpd #顯示進程在哪一個cpu上運行。查看有沒有綁定成功可以cat tasks看看有沒有這個pid
如果進程顯示cpu未改變,可以進行壓力測試重新調度
[root@localhost ~]# ab -n 10000 -c 300 http://127.0.0.1/index.php
用watch監測一個命令的運行結果發現16380已經綁定在子域上了。
[root@localhost domain1]# watch -n 0.5 `ps -e -o psr,pid,cmd |grep httpd`
注:以上數據是通過echo過來的,重啟之后都會失效的
另一種手動方式將16380綁定到CPU上
[root@localhost domain1]# taskset -p -c 0 16380 #-p一定要在-c前頭
pid 16380's current affinity list: 0 顯示16380號進程之前可以運行在0號cpu上
pid 16380's new affinity list: 0 現在可以運行在0號cpu上
四、Linux內存子系統及常用調優參數
內存子系統及常用調優參數:https://blog.csdn.net/Celeste7777/article/details/49560401
內存域
程序能夠運行的地址范圍大小由CPU的位數決定,這個地址范圍稱為虛擬地址空間,該空間中的某一個地址稱之為虛擬地址。CPU是32位還是64位主要是依據CPU的字組大小(每次能夠處理的數據量)而來。例如32位CPU可以支持的地址范圍是0~0xFFFFFFFF (2^32=4G),而64位CPU可以支持的地址范圍為0~0xFFFFFFFFFFFFFFFF (2^32個4G)。
一般而言在32位系統中,較低地址空間的1GB(虛地址0xC0000000到0xFFFFFFFF)供內核使用,稱為內核空間。而較高地址空間的3GB(虛地址0x00000000到0xBFFFFFFF)供各個進程使用,稱為用戶空間;因為每個進程可以通過系統調用進入內核,因此,內核空間由系統內的所有進程共享;從單個進程的角度來看,每個進程都可以擁有4GB的虛擬地址空間(也叫做虛擬內存)。再深入內核空間看,其低地址位置有16MB給DMA(ZONE_DMA),從16M到896M才是內核可以直接訪問的地址空間(ZONE_NORMAL),從896M到1G這段空間是預留的物理地址空間(Reserved)。內核不能直接訪問用戶空間,要想訪問必須把其中的一段內容映射到Reserved來,在Reserved中保存即將要訪問那段內存的地址編碼,內核才能去訪問,所以在32位系統上內核不能直接訪問大於1G的內存地址。
在Linux64位系統中,低地址空間的1G內存都給了DMA,這個時候DMA的尋址能力就大大加強了;1G以上的地址空間給划分了ZONE_NORMAL,這段空間都可以被內核直接訪問。所以在64位上,內核可以直接訪問大於1G的內存地址,不再需要額外的步驟,效率和性能上也大大增加。
Linux的虛擬內存子系統包含了以下幾個功能模塊:
1、slab allocator
2、buddy system
3、kswapd
4、pdflush
5、mmu
buddy system是工作在MMU之上的,而slab allocator又是工作在buddy system之上的。
MMU(Memory Management Unit):內存管理元
物理地址(Physical Address):指的是CPU外部地址總線上尋址物理內存的地址信號,是地址變換的最終結果。
虛擬地址(Virtual Address):也稱為邏輯地址,由段選擇符和段內偏移地址兩個部分組成。
線性地址(Linear Address):是VA到PA變換的中間層。程序代碼會產生LA(段中的偏移地址),加上相應段的基地址就生成了一個線性地址。如果啟用了分頁機制,那么線性地址可以再經變換以產生一個PA。若沒有啟用分頁機制,那么線性地址直接就是PA。
內存管理單元是CPU中用來管理虛擬存儲器、物理存儲器的控制線路的組件,同時也負責將虛擬地址映射為物理地址,以及提供硬件機制的內存訪問授權。ARM出品的CPU,MMU作為一個協處理器存在。ARM MMU提供的分頁機制有1K/4K/64K 3種模式;INTEL出品的80386CPU或者更新的CPU中都集成有MMU,X86 MMU提供的尋址模式有4K/2M/4M的page模式(根據不同的CPU,提供不同的能力)。