在Linux系統(tǒng)中,由于成本的限制,往往會(huì)存在資源上的不足,例如 CPU、內(nèi)存、網(wǎng)絡(luò)、IO 性能。本文,就對(duì) Linux 進(jìn)程和 CPU 的原理進(jìn)行分析,總結(jié)出 CPU 性能優(yōu)化的方法。
1. 分析手段
在理解平均負(fù)載之前,先要理清楚 Linux 下的進(jìn)程狀態(tài)。
1.1. 進(jìn)程狀態(tài)
1.1.1. R (TASK_RUNNING),可執(zhí)行狀態(tài)
只有在該狀態(tài)的進(jìn)程才可能在 CPU 上運(yùn)行。而同一時(shí)刻可能有多個(gè)進(jìn)程處于可執(zhí)行狀態(tài),這些進(jìn)程的 task_struct 結(jié)構(gòu)(進(jìn)程控制塊)被放入對(duì)應(yīng) CPU 的可執(zhí)行隊(duì)列中(一個(gè)進(jìn)程最多只能出現(xiàn)在一個(gè) CPU 的可執(zhí)行隊(duì)列中)。進(jìn)程調(diào)度器的任務(wù)就是從各個(gè) CPU 的可執(zhí)行隊(duì)列中分別選擇一個(gè)進(jìn)程在該 CPU 上運(yùn)行。
很多操作系統(tǒng)教科書(shū)將正在 CPU 上執(zhí)行的進(jìn)程定義為 RUNNING 狀態(tài)、而將可執(zhí)行但是尚未被調(diào)度執(zhí)行的進(jìn)程定義為READY狀態(tài),這兩種狀態(tài)在linux下統(tǒng)一為 TASK_RUNNING 狀態(tài)。
1.1.2. S (TASK_INTERRUPTIBLE),可中斷的睡眠狀態(tài)
處于這個(gè)狀態(tài)的進(jìn)程因?yàn)榈却衬呈录陌l(fā)生(比如等待 socket 連接、等待信號(hào)量),而被掛起。這些進(jìn)程的 task_struct 結(jié)構(gòu)被放入對(duì)應(yīng)事件的等待隊(duì)列中。當(dāng)這些事件發(fā)生時(shí)
(由外部中斷觸發(fā)、或由其他進(jìn)程觸發(fā)),對(duì)應(yīng)的等待隊(duì)列中的一個(gè)或多個(gè)進(jìn)程將被喚醒。通過(guò) ps 命令我們會(huì)看到,一般情況下,進(jìn)程列表中的絕大多數(shù)進(jìn)程都處于 TASK_INTERRUPTIBLE 狀態(tài)(除非機(jī)器的負(fù)載很高)。畢竟 CPU 就這么一兩個(gè),進(jìn)程動(dòng)輒幾十上百個(gè),如果不是絕大多數(shù)進(jìn)程都在睡眠,CPU 又怎么響應(yīng)得過(guò)來(lái)。
1.1.3. D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態(tài)
與 TASK_INTERRUPTIBLE 狀態(tài)類似,進(jìn)程處于睡眠狀態(tài),但是此刻進(jìn)程是不可中斷的。
不可中斷,指的并不是 CPU 不響應(yīng)外部硬件的中斷,而是指進(jìn)程不響應(yīng)異步信號(hào)。
絕大多數(shù)情況下,進(jìn)程處在睡眠狀態(tài)時(shí),總是應(yīng)該能夠響應(yīng)異步信號(hào)的。否則你將驚奇的發(fā)現(xiàn),kill -9 竟然殺不死一個(gè)正在睡眠的進(jìn)程了!于是我們也很好理解,為什么 ps 命令看到的進(jìn)程幾乎不會(huì)出現(xiàn) TASK_UNINTERRUPTIBLE 狀態(tài),而總是 TASK_INTERRUPTIBLE 狀態(tài)。
而 TASK_UNINTERRUPTIBLE 狀態(tài)存在的意義就在于,內(nèi)核的某些處理流程是不能被打斷的。如果響應(yīng)異步信號(hào),程序的執(zhí)行流程中就會(huì)被插入一段用于處理異步信號(hào)的流程(這個(gè)插入的流程可能只存在于內(nèi)核態(tài),也可能延伸到用戶態(tài)),于是原有的流程就被中斷了。
(參見(jiàn)《linux 內(nèi)核異步中斷淺析》) 在進(jìn)程對(duì)某些硬件進(jìn)行操作時(shí)(比如進(jìn)程調(diào)用 read 系統(tǒng)調(diào)用對(duì)某個(gè)設(shè)備文件進(jìn)行讀操作,而 read 系統(tǒng)調(diào)用最終執(zhí)行到對(duì)應(yīng)設(shè)備驅(qū)動(dòng)的代碼,并與對(duì)應(yīng)的物理設(shè)備進(jìn)行交互),可能需要使用 TASK_UNINTERRUPTIBLE 狀態(tài)對(duì)進(jìn)程進(jìn)行保護(hù),以避免進(jìn)程與設(shè)備交互的過(guò)程被打斷,造成設(shè)備陷入不可控的狀態(tài)。這種情況下的 TASK_UNINTERRUPTIBLE 狀態(tài)總是非常短暫的,通過(guò) ps 命令基本上不可能捕捉到。
1.1.4. T (TASK_STOPPED or TASK_TRACED),暫停狀態(tài)或跟蹤狀態(tài)
向進(jìn)程發(fā)送一個(gè) SIGSTOP 信號(hào),它就會(huì)因響應(yīng)該信號(hào)而進(jìn)入 TASK_STOPPED 狀態(tài)(除非該進(jìn)程本身處于 TASK_UNINTERRUPTIBLE 狀態(tài)而不響應(yīng)信號(hào))。(SIGSTOP 與 SIGKILL 信號(hào)一樣,是非常強(qiáng)制的。不允許用戶進(jìn)程通過(guò) signal 系列的系統(tǒng)調(diào)用重新設(shè)置對(duì)應(yīng)的信號(hào)處理函數(shù)。)
向進(jìn)程發(fā)送一個(gè) SIGCONT 信號(hào),可以讓其從 TASK_STOPPED 狀態(tài)恢復(fù)到 TASK_RUNNING 狀態(tài)。
當(dāng)進(jìn)程正在被跟蹤時(shí),它處于 TASK_TRACED 這個(gè)特殊的狀態(tài)?!罢诒桓櫋敝傅氖沁M(jìn)程暫停下來(lái),等待跟蹤它的進(jìn)程對(duì)它進(jìn)行操作。比如在 gdb 中對(duì)被跟蹤的進(jìn)程下一個(gè)斷點(diǎn),進(jìn)程在斷點(diǎn)處停下來(lái)的時(shí)候就處于 TASK_TRACED 狀態(tài)。而在其他時(shí)候,被跟蹤的進(jìn)程還是處于前面提到的那些狀態(tài)。
對(duì)于進(jìn)程本身來(lái)說(shuō),TASK_STOPPED 和 TASK_TRACED 狀態(tài)很類似,都是表示進(jìn)程暫停下來(lái)。而 TASK_TRACED 狀態(tài)相當(dāng)于在 TASK_STOPPED 之上多了一層保護(hù),處于 TASK_TRACED 狀態(tài)的進(jìn)程不能響應(yīng) SIGCONT 信號(hào)而被喚醒。只能等到調(diào)試進(jìn)程通過(guò) ptrace 系統(tǒng)調(diào)用執(zhí)行 PTRACE_CONT、PTRACE_DETACH 等操作(通過(guò) ptrace 系統(tǒng)調(diào)用的參數(shù)指定操作),或調(diào)試進(jìn)程退出,被調(diào)試的進(jìn)程才能恢復(fù) TASK_RUNNING 狀態(tài)。
1.1.5. Z (TASK_DEAD - EXIT_ZOMBIE),退出狀態(tài),進(jìn)程成為僵尸進(jìn)程
進(jìn)程在退出的過(guò)程中,處于 TASK_DEAD 狀態(tài)。在這個(gè)退出過(guò)程中,進(jìn)程占有的所有資源將被回收,除了 task_struct 結(jié)構(gòu)(以及少數(shù)資源)以外。于是進(jìn)程就只剩下 task_struct 這么個(gè)空殼,故稱為僵尸。之所以保留 task_struct,是因?yàn)?task_struct 里面保存了進(jìn)程的退出碼、以及一些統(tǒng)計(jì)信息。而其父進(jìn)程很可能會(huì)關(guān)心這些信息。比如在 shell 中,$?變量就保存了最后一個(gè)退出的前臺(tái)進(jìn)程的退出碼,而這個(gè)退出碼往往被作為 if 語(yǔ)句的判斷條件。
當(dāng)然,內(nèi)核也可以將這些信息保存在別的地方,而將 task_struct 結(jié)構(gòu)釋放掉,以節(jié)省一些空間。但是使用 task_struct 結(jié)構(gòu)更為方便,因?yàn)樵趦?nèi)核中已經(jīng)建立了從 pid 到 task_struct 查找關(guān)系,還有進(jìn)程間的父子關(guān)系。釋放掉 task_struct,則需要建立一些新的數(shù)據(jù)結(jié)構(gòu),以便讓父進(jìn)程找到它的子進(jìn)程的退出信息。
父進(jìn)程可以通過(guò) wait 系列的系統(tǒng)調(diào)用(如 wait4、waitid)來(lái)等待某個(gè)或某些子進(jìn)程的退出,并獲取它的退出信息。然后 wait 系列的系統(tǒng)調(diào)用會(huì)順便將子進(jìn)程的尸體(task_struct)也釋放掉。
子進(jìn)程在退出的過(guò)程中,內(nèi)核會(huì)給其父進(jìn)程發(fā)送一個(gè)信號(hào),通知父進(jìn)程來(lái)“收尸”。這個(gè)信號(hào)默認(rèn)是 SIGCHLD,但是在通過(guò) clone 系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程時(shí),可以設(shè)置這個(gè)信號(hào)。
1.2. 平均負(fù)載
單位時(shí)間內(nèi),系統(tǒng)處于可運(yùn)行狀態(tài)和不可中斷狀態(tài)的平均進(jìn)程數(shù),也就是平均活躍進(jìn)程數(shù),它和 CPU 使用率并沒(méi)有直接關(guān)系。
既然是平均的活躍進(jìn)程數(shù),那么最理想的,就是每個(gè)cpu 上都剛好運(yùn)行著一個(gè)進(jìn)程,這樣每個(gè) cpu 都得到了充分利用,比如當(dāng)平均負(fù)載 2時(shí),意味著什么呢?
1、 在只有2 個(gè) CPU的系統(tǒng)上,意味著所有的 CPU都剛好被完全占用
2、 在 4 個(gè)CPU的系統(tǒng)上,意味著 CPU 有 50%的空閑
3、 而在只有 1 個(gè)CPU 的系統(tǒng)上,則意味著有一半的進(jìn)程競(jìng)爭(zhēng)不到 CPU
1.2.1. 平均負(fù)載多少合理?
平均負(fù)載最理想的情況是等于 CPU 個(gè)數(shù)。查看系統(tǒng) CPU 的命令如下:
cat /proc/cpuinfo
Figure 1 四核 CPU 查看平均負(fù)載的命令:
給了我們?nèi)齻€(gè)不同時(shí)間間隔的平均值,給我們提供了分析系統(tǒng)負(fù)載趨勢(shì)的數(shù)據(jù)來(lái)源,讓我們更全面、更立體地理解目前的負(fù)載情況。
· 1 分鐘、5 分鐘、15 分鐘 的三個(gè)值基本相同,或者相差不大,說(shuō)明系統(tǒng)負(fù)載很平
· 如果1 分鐘的值遠(yuǎn)小于15 分鐘 的值,說(shuō)明系統(tǒng)最近 1 分鐘的負(fù)載在減少,而過(guò)去
15 分鐘內(nèi)卻有很大的負(fù)載
· 如果1 分鐘 的值遠(yuǎn)大于 15 分鐘的值,就說(shuō)明最近 1 分鐘的負(fù)載在增加。一旦 1 分鐘的平均負(fù)載接近或超過(guò)了 CPU 的個(gè)數(shù),就意味著系統(tǒng)正在發(fā)生過(guò)載的問(wèn)題。
uptime 命令在有些嵌入式設(shè)備中,會(huì)被裁減掉,但是可以通過(guò) proc 文件系統(tǒng)來(lái)獲取。命令:
cat /proc/loadavg
很顯然,當(dāng)前命令展示的平均負(fù)載在 CPU 為 4 個(gè)時(shí)候已經(jīng)過(guò)載
1.2.2. 平均負(fù)載與 CPU 使用率
平均負(fù)載不僅包括了正在使用 CPU 的進(jìn)程,還包括了等待 CPU 和等待 I/O 的進(jìn)程。
CPU 使用率是指單位時(shí)間內(nèi) CPU 繁忙情況的統(tǒng)計(jì),跟平均負(fù)載并不一定完全對(duì)應(yīng)。比如:
· CPU 密集型進(jìn)程,使用大量 CPU 會(huì)導(dǎo)致平均負(fù)載升高,此時(shí)這兩者是一致的;
· I/O 密集型進(jìn)程,等待 I/O 也會(huì)導(dǎo)致平均負(fù)載升高,但 CPU 使用率不一定很高
· 大量等待 CPU 的進(jìn)程調(diào)度也會(huì)導(dǎo)致平均負(fù)載升高,此時(shí)的 CPU 使用率也會(huì)比較高。
1.3. CPU 上下文切換
在每個(gè)任務(wù)運(yùn)行前, CPU 都需要知道任務(wù)從哪里加載、又從哪里開(kāi)始運(yùn)行、也就是說(shuō),需要系統(tǒng)事先給他設(shè)置好 CPU 寄存器和程序計(jì)數(shù)器(Program Counter, PC)
· CPU 寄存器:是 CPU 內(nèi)置的容量小、但速度極快的內(nèi)存。
· 程序計(jì)數(shù)器:是用來(lái)存儲(chǔ) CPU 正在執(zhí)行的指令位置、或者即將執(zhí)行的下一條指令位置。
它們都是 CPU 在運(yùn)行任何任務(wù)前,比如的依賴環(huán)境,因此也被叫做 CPU 上下文。
· 上下文切換:就是先把前一個(gè)任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計(jì)數(shù)器)保存起來(lái),然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置,運(yùn)行新任務(wù)。
· CPU 的上下文切換可以分為進(jìn)程上下文切換、線程上下文切換以及中斷上下文切換。
1.3.1. 進(jìn)程上下文切換
Linux 按照特權(quán)等級(jí),把進(jìn)程的運(yùn)行空間分為內(nèi)核空間和用戶空間
· 內(nèi)核空間(Ring 0)具有最高權(quán)限,可以直接訪問(wèn)所有資源。
· 用戶空間(Ring 3)只能訪問(wèn)受限資源,不能直接訪問(wèn)內(nèi)存等硬件設(shè)備,必須通過(guò)系統(tǒng)調(diào)用陷入到內(nèi)核中,才能訪問(wèn)這些特權(quán)資源。
1.3.2. 進(jìn)程上下文切換和系統(tǒng)調(diào)用的區(qū)別
進(jìn)程是由內(nèi)核來(lái)管理和調(diào)度的,進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài)。所以,進(jìn)程的上下文不僅包括了虛擬內(nèi)存、棧、全局變量等用戶空間的資源,還包括了內(nèi)核堆棧、寄存器等內(nèi)核空間的狀態(tài)。
系統(tǒng)調(diào)用過(guò)程中,并不涉及到虛擬內(nèi)存等進(jìn)程用戶態(tài)的資源,也不會(huì)切換進(jìn)程。
· 進(jìn)程上下文切換,是指從一個(gè)進(jìn)程切換到另一個(gè)進(jìn)程進(jìn)行。
· 系統(tǒng)調(diào)用過(guò)程中一直是同一個(gè)進(jìn)程在運(yùn)行。
因此,進(jìn)程的上下文切換比系統(tǒng)調(diào)用時(shí)多了一步:在保存當(dāng)前進(jìn)程的內(nèi)核狀態(tài)和 CPU 寄存器之前,需要先把該進(jìn)程的虛擬內(nèi)存、棧等保存下來(lái);而加載了下一個(gè)進(jìn)程的內(nèi)核態(tài)后,還需要刷新進(jìn)程的虛擬內(nèi)存和用戶棧。
1.3.3. 什么時(shí)候會(huì)切換進(jìn)程上文
· 進(jìn)程執(zhí)行終止,它之前使用的 CPU 會(huì)釋放出來(lái),這時(shí)再?gòu)木途w隊(duì)列里,拿一個(gè)新的進(jìn)程過(guò)來(lái)運(yùn)行。
· 當(dāng)某個(gè)進(jìn)程的時(shí)間片耗盡了,就會(huì)被系統(tǒng)掛起,切換到其他正在等待 CPU 的進(jìn)程進(jìn)行
· 進(jìn)程在系統(tǒng)資源不足(比如內(nèi)存不足)時(shí),等到資源滿足后才可以運(yùn)行,這個(gè)時(shí)候進(jìn)程也會(huì)被掛起,并由系統(tǒng)調(diào)度其他進(jìn)程運(yùn)行。
· 當(dāng)進(jìn)程通過(guò)睡眠函數(shù) sleep 這樣的方法將自己主動(dòng)掛起時(shí),自然也會(huì)重新調(diào)度。
· 當(dāng)有優(yōu)先級(jí)更高的進(jìn)程運(yùn)行時(shí),為了保證高優(yōu)先級(jí)進(jìn)程的運(yùn)行,當(dāng)前進(jìn)程會(huì)被掛起,由高優(yōu)先級(jí)進(jìn)程來(lái)運(yùn)行。
· 發(fā)生硬件中斷時(shí),CPU 上的進(jìn)程會(huì)被中斷掛起,轉(zhuǎn)而執(zhí)行內(nèi)核中的中斷程序服務(wù)。
1.3.4. 線程上下文切換
線程和進(jìn)程的區(qū)別
· 線程是調(diào)度的基本單位,而進(jìn)程則是資源擁有的基本單位。
· 當(dāng)進(jìn)程只有一個(gè)線程時(shí),可以認(rèn)為進(jìn)程就等于線程。
· 當(dāng)進(jìn)程擁有多個(gè)線程時(shí),這些線程會(huì)共享相同的虛擬內(nèi)存和全局變量等資源。這些資源在上下文切換時(shí)是不需要修改的。
· 線程也有自己的私有數(shù)據(jù),比如棧和寄存器等,這些在上下文切換時(shí)也是需要保存的。
線程的上下文切換兩種情況
· 前后兩個(gè)線程屬于不同進(jìn)程。此時(shí),因?yàn)橘Y源不共享,所以切換過(guò)程就跟進(jìn)程上下文切換是一樣的。
· 前后兩個(gè)線程屬于同一個(gè)進(jìn)程。此時(shí),因?yàn)樘摂M內(nèi)存是共享的,所以在切換時(shí),虛擬內(nèi)存這些資源就保持不動(dòng),只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)。
1.3.5. 中斷上下文切換
中斷處理會(huì)打斷進(jìn)程的正常調(diào)度和執(zhí)行。在打斷其他進(jìn)程時(shí),需要將進(jìn)程當(dāng)前的狀態(tài)保存下來(lái),中斷結(jié)束后,進(jìn)程仍然可以從原來(lái)的狀態(tài)恢復(fù)運(yùn)行。
進(jìn)程上下文切換和中斷上下文切換的區(qū)別
· 中斷上下文切換并不涉及到進(jìn)程的用戶態(tài)。所以,即便中斷過(guò)程打斷了一個(gè)正處在用戶態(tài)的進(jìn)程,也不需要保存和恢復(fù)這個(gè)進(jìn)程的虛擬內(nèi)存、全局變量等用戶態(tài)資源。中斷上下文,其實(shí)只包括內(nèi)核態(tài)中斷服務(wù)程序執(zhí)行所必須的狀態(tài),包括 CPU 寄存器、內(nèi)核堆棧、硬件中斷參數(shù)等。
· 對(duì)同一個(gè) CPU 來(lái)說(shuō),中斷處理比進(jìn)程擁有更高的優(yōu)先級(jí)。
進(jìn)程上下文切換和中斷上文切換的相同之處
· 都需要消耗 CPU,切換次數(shù)過(guò)多會(huì)耗費(fèi)大量 CPU,甚至嚴(yán)重降低系統(tǒng)的整體性能。
1.3.6. CPU 上下文切換小結(jié)
· CPU 上下文切換,是保證 Linux 系統(tǒng)正常工作的核心功能之一,一般情況下不需要我們特別關(guān)注。
· 但過(guò)多的上下文切換,會(huì)把 CPU 時(shí)間消耗在寄存器、內(nèi)核棧以及虛擬內(nèi)存等數(shù)據(jù)的保存和恢復(fù)上,從而縮短進(jìn)程真正運(yùn)行的時(shí)間,導(dǎo)致系統(tǒng)的整體性能大幅下降
1.3.7. 如何查看系統(tǒng)的上下文切換
常用的系統(tǒng)性能分析工具,主要用來(lái)分析系統(tǒng)的內(nèi)存使用情況,也常用來(lái)分析
CPU 上下文切換和中斷次數(shù)。
Figure 2 每隔 2 秒輸出一組數(shù)據(jù)
需要特別關(guān)注的四列內(nèi)容:
· cs (context switch):每秒上下文切換的次數(shù)。
· in (interrupt):每秒中斷的次數(shù)。
· r (Running or Runnable) :就緒隊(duì)列的長(zhǎng)度,也就是正在運(yùn)行和等待 CPU 的進(jìn)程數(shù)。
· b (Blocked):處在不可中斷睡眠狀態(tài)的進(jìn)程數(shù)。
在嵌入式 Linux 設(shè)備中,一般 vmstat 工具是不存在的。所以如果想要 vmstat 工具,可以自己實(shí)現(xiàn)代碼,他的原理是獲取/proc/diskstats 和/proc/slabinfo 的信息組合而成。實(shí)現(xiàn)代碼見(jiàn) procps 工具
vmstat 只給出了系統(tǒng)總體的上下文切換情況,并不能查看每個(gè)進(jìn)程的上下文切換情況。
:查看某個(gè)進(jìn)程中線程的上下文切換情況,下圖查看的是 hicore 進(jìn)程中所有的線程上下文切換情況。
關(guān)注兩列內(nèi)容:
1. 自愿上下文切換:進(jìn)程無(wú)法獲取所需資源,導(dǎo)致的上下文切換。比如, I/O、內(nèi)存等系統(tǒng)資源不足時(shí)。
2. 非自愿上下文切換:進(jìn)程由于時(shí)間片已到等原因,被系統(tǒng)強(qiáng)制調(diào)度,進(jìn)而發(fā)生的上下文切換。比如,大量進(jìn)程都在爭(zhēng)搶 CPU 時(shí)。
1.3.9. Procps 工具
procps 是一組命令行和全屏工具,是由內(nèi)核動(dòng)態(tài)生成的一個(gè) “偽” 文件系統(tǒng),可以提供進(jìn)程表中條目狀態(tài)的信息。該文件系統(tǒng)為內(nèi)核數(shù)據(jù)結(jié)構(gòu)提供了一個(gè)簡(jiǎn)易接口,procps 程序通常就集中在這個(gè)描述了系統(tǒng)進(jìn)程運(yùn)行狀態(tài)的數(shù)據(jù)結(jié)構(gòu)上。
procps 包括以下程序:
· free - 報(bào)告系統(tǒng)中可用內(nèi)存和已用內(nèi)存的數(shù)量
· kill - 基于 PID,向進(jìn)程發(fā)送信號(hào)
· pgrep - 根據(jù)名稱或其他屬性列出進(jìn)程
· pkill - 根據(jù)名稱或其他屬性向進(jìn)程發(fā)送信號(hào)
· pmap - 報(bào)告進(jìn)程的內(nèi)存映射
· ps - 報(bào)告進(jìn)程信息
· pwdx - 報(bào)告進(jìn)程的當(dāng)前目錄
· skill - pgrep/pkill 的過(guò)時(shí)版本
· slabtop - 實(shí)時(shí)顯示內(nèi)核 slab 緩存信息
· snice - Renice 一個(gè)進(jìn)程
· sysctl -運(yùn)行時(shí)內(nèi)核參數(shù)的讀或?qū)?/p>
· tload - 系統(tǒng)負(fù)載均值的可視化
· top - 正運(yùn)行進(jìn)程的實(shí)時(shí)動(dòng)態(tài)視圖
· uptime - 顯示系統(tǒng)的已運(yùn)行時(shí)間和負(fù)載情況
· vmstat - 報(bào)告虛擬內(nèi)存統(tǒng)計(jì)信息
· w - 報(bào)告登錄用戶,以及他們正在做什么
· watch - 定期執(zhí)行程序,顯示全屏輸出官網(wǎng)地址:http://procps.sourceforge.net/
1.3.10. sysstat 工具
在嵌入式 Linux 設(shè)備中同樣也不存在該工具,busybox 中也沒(méi)有相關(guān)命令。需要安裝 Linux 性能監(jiān)控工具 sysstat,他是一個(gè)工具集,包括 sar、sadf、mpstat、iostat、pidstat 等,這些工具可以監(jiān)控系統(tǒng)性能和使用情況。各工具的作用如下:
1. iostat - 提供 CPU 統(tǒng)計(jì),存儲(chǔ) I/O 統(tǒng)計(jì)(磁盤(pán)設(shè)備,分區(qū)及網(wǎng)絡(luò)文件系統(tǒng))
2. mpstat - 提供單個(gè)或組合 CPU 相關(guān)統(tǒng)計(jì)
3. pidstat - 提供 Linux 進(jìn)程級(jí)別統(tǒng)計(jì):I/O、CPU、內(nèi)存等
4. sar - 收集、報(bào)告、保存系統(tǒng)活動(dòng)信息:CPU、內(nèi)存、磁盤(pán)、中斷、網(wǎng)絡(luò)接口、TTY、內(nèi)核表等
5. sadc - 系統(tǒng)活動(dòng)數(shù)據(jù)收集器,作為 sar 后端使用
6. sa1 - 收集系統(tǒng)活動(dòng)日常數(shù)據(jù),并二進(jìn)制格式存儲(chǔ),它作為 sadc 的工具的前端,可以通過(guò) cron 來(lái)調(diào)用
7. sa2 - 生成系統(tǒng)每日活動(dòng)報(bào)告,同樣可作為 sadc 的工具的前端,可以通過(guò) cron 來(lái)調(diào)用
8. sadf - 可以以 CSV、XML 格式等顯示 sar 收集的性能數(shù)據(jù),這樣非常方便的將系統(tǒng)數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫(kù)中,或?qū)氲?Excel 中來(lái)生成圖表
9. nfsiostat-sysstat: 提供 NFS I/O 統(tǒng)計(jì)
10. cifsiostat: 提供 CIFS 統(tǒng)計(jì)
sysstat 功能強(qiáng)大,功能也在不斷的增強(qiáng),每個(gè)版本提供了不同的功能,可以到 sysstat 官網(wǎng)了 解 工 具 最 先 發(fā) 展 情 況 和 獲 得 相 應(yīng) 的 幫 助 手 冊(cè) 。 官 網(wǎng) 地 址 :
1.3.11. 中斷
中斷是一種異步的事件處理機(jī)制,可以提高系統(tǒng)的并發(fā)處理能力。中斷處理程序會(huì)打斷其他進(jìn)程的運(yùn)行,為了減少對(duì)正常進(jìn)程運(yùn)行調(diào)度的影響,中斷處理程序就需要盡可能快地運(yùn)行。
Linux 將中斷處理過(guò)程分成了兩個(gè)階段,也就是上半部和下半部。
· 上半部用來(lái)快速處理中斷,它在中斷禁止模式下運(yùn)行,主要處理跟硬件緊密相關(guān)的或時(shí)間敏感的工作。
· 下半部用來(lái)延時(shí)處理上半部未完成的工作,通常以內(nèi)核線程的方式運(yùn)行。
/proc/interrupts:查看硬中斷發(fā)生的類型
硬件中斷發(fā)生頻繁,是件很消耗 CPU 資源的事情,Linux 默認(rèn)情況下是將所有的硬件中斷都綁定在 CPU0 上,在多核 CPU 條件下如果有辦法把大量硬件中斷分配給不同的
CPU (core) 處理顯然能很好的平衡性能。
1.3.13. 根據(jù)上下文切換的類型做具體分析
· 自愿上下文切換變多,說(shuō)明進(jìn)程都在等待資源,有可能發(fā)生 I/O 等其他問(wèn)題
· 非自愿上下文切換變多,說(shuō)明進(jìn)程都在被強(qiáng)制調(diào)度,也就是都在爭(zhēng)搶 CPU,說(shuō)明 CPU 的確成了瓶頸
· 中斷次數(shù)變多,說(shuō)明 CPU 被中斷處理程序占用,還需要通過(guò)查看 /proc/interrupts 文件來(lái)分析具體的中斷類型。
1.4. CPU 使用率
/proc/stat,提供的是系統(tǒng)的 CPU 和任務(wù)統(tǒng)計(jì)信息。這個(gè)信息非常的原始
CPU 使用率相關(guān)的重要指標(biāo)
· 第一列:user(us),代表用戶態(tài) CPU 時(shí)間。
· 第二列:nice(ni),代表低優(yōu)先級(jí)用戶態(tài) CPU 時(shí)間,也就是進(jìn)程的 nice 值被調(diào)整為 1-
19 之間時(shí)的 CPU 時(shí)間。nice 可取值范圍是 -20 到 19, 數(shù)值越大,優(yōu)先級(jí)反而越低
· 第三列:system (sys),代表內(nèi)核態(tài) CPU 時(shí)間。
· 第四列:idle(us),代表空閑時(shí)間。注意,這里它不包括等待 I/O 的時(shí)間(iowait)。
· 第五列:iowait(wa),代表等待 I/O 的 CPU 時(shí)間。
· 第六列:irq(hi),代表處理硬中斷的 CPU 時(shí)間
· 第七列:softirq(si),代表處理軟中斷的 CPU 時(shí)間。
真正查看 CPU 使用率的命令是通過(guò) top 命令
1.5. 軟中斷
提供了軟中斷的運(yùn)行情況。
· 注意軟中斷的類型,也就是第一列內(nèi)容。
· 注意同一種軟中斷在不同 CPU 上的分布情況,也就是同一行內(nèi)容。
· 軟中斷實(shí)際上是以內(nèi)核線程的方式運(yùn)行的,每個(gè) CPU 都對(duì)應(yīng)一個(gè)軟中斷內(nèi)核線程,這個(gè)軟中斷內(nèi)核線程就叫做 ksoftirqd/CPU 編號(hào)
2. 優(yōu)化方法
2.1 CPU 使用率
CPU 使用率描述了非空閑時(shí)間占總 CPU 時(shí)間的百分比,根據(jù) CPU 上運(yùn)行任務(wù)的不同,又被分為用戶 CPU、系統(tǒng) CPU、等待 I/O CPU、軟中斷和硬中斷等。
· 用戶 CPU 使用率,包括用戶態(tài) CPU 使用率(user) 和低優(yōu)先級(jí)用戶態(tài) CPU 使用率 (nice),表示 CPU 在用戶態(tài)運(yùn)行的時(shí)間百分比。用戶 CPU 使用率高,通常說(shuō)明有應(yīng)用程序比較繁忙。
· 系統(tǒng) CPU 使用率,表示 CPU 在內(nèi)核態(tài)運(yùn)行的時(shí)間百分比(不包括中斷)。系統(tǒng) CPU 使用率高,說(shuō)明內(nèi)核比較繁忙。
· 等待 I/O 的 CPU 使用率,通常也稱為 iowait,表示等待 I/O 的時(shí)間百分比。iowait 高,通常說(shuō)明系統(tǒng)與硬件設(shè)備的 I/O 交互時(shí)間比較長(zhǎng)。
· 軟中斷和硬中斷的 CPU 使用率,分別表示內(nèi)核調(diào)用軟中斷處理程序、硬中斷處理程序的時(shí)間百分比。它們的使用率高,通常說(shuō)明系統(tǒng)發(fā)生了大量的中斷。
2.2 平均負(fù)載(Load Average)
平均負(fù)載,也就是系統(tǒng)的平均活躍進(jìn)程數(shù),它反映了系統(tǒng)的整體負(fù)載情況,主要包括三個(gè)數(shù)值,分別指過(guò)去 1 分鐘、過(guò)去 5 分鐘和過(guò)去 15 分鐘的平均復(fù)制子。
理想情況下,平均負(fù)載等于邏輯 CPU 個(gè)數(shù),這表示每個(gè) CPU 都恰好被充分利用。如果平均負(fù)載大于邏輯 CPU 個(gè)數(shù),就表示負(fù)載比較重了。
2.3 進(jìn)程上下文切換
· 無(wú)法獲取資源而導(dǎo)致的自愿上下文切換。
· 被系統(tǒng)強(qiáng)制調(diào)度導(dǎo)致的非自愿上下文切換。
2.4 CPU 緩存的命中率
由于 CPU 發(fā)展的速度遠(yuǎn)快于內(nèi)存的發(fā)展, CPU 的處理速度就比內(nèi)存的訪問(wèn)速度快得多。這樣,CPU 在訪問(wèn)內(nèi)存的時(shí)候,免不了要等待內(nèi)存的響應(yīng)。為了協(xié)調(diào)這兩者巨大的性能差距,CPU 緩存(通常是多級(jí)緩存)就出現(xiàn)了。
根據(jù)不斷增長(zhǎng)的熱點(diǎn)數(shù)據(jù),這些緩存按照大小不同分為 L1、L2、L3 等三級(jí)緩存,其中
L1 和 L2 常用在單核中,L3 則用在多核中。
從 L1 到 L3,三級(jí)緩存的大小依次增大,相應(yīng)的,性能依次降低(當(dāng)然比內(nèi)存還是好
得多)。而它們的命中率,衡量的是 CPU 緩存的復(fù)用情況,命中率越高,則表示性能越好。
2.5 tcmalloc 替換 ptmalloc
2.5.1 ptmalloc
Ptmalloc 采用主-從分配區(qū)的模式,當(dāng)一個(gè)線程需要分配資源的時(shí)候,從鏈表中找到一個(gè)沒(méi)加鎖的分配區(qū),在進(jìn)行內(nèi)存分配。
小內(nèi)存分配
在 ptmalloc 內(nèi)部,內(nèi)存塊采用 chunk 管理,并且將大小相似的 chunk 用鏈表管理,一個(gè)鏈表被稱為一個(gè) bin。前 64 個(gè) bin 里,相鄰的 bin 內(nèi)的 chunk 大小相差 8 字節(jié),稱為 small bin,后面的是 large bin,large bin 里的 chunk 按先大小,再最近使用的順序排列,每次分配都找一個(gè)最小的能夠使用的 chunk。
Chunk 的結(jié)構(gòu)如上所示,A 位表示是不是在主分配區(qū),M 表示是不是 mmap 出來(lái)的,P 表示上一個(gè)內(nèi)存緊鄰的 chunk 是否在使用,如果沒(méi)在使用,則 size of previous
chunk 是上一個(gè) chunk 的大小,否則無(wú)意義(而且被用作被分配出去的內(nèi)存了),正式根據(jù)
P 標(biāo)記位和 size of previous chunk 在 free 內(nèi)存塊的時(shí)候來(lái)進(jìn)行 chunk 合并的。當(dāng)然,如果 chunk 空閑,mem 里還記錄了一些指針用于索引臨近大小的 chunk 的,實(shí)現(xiàn)原理就不復(fù)述了,知道大致作用就行。
在 free 的時(shí)候,ptmalloc 會(huì)檢查附近的 chunk,并嘗試把連續(xù)空閑的 chunk 合并成一個(gè)大的 chunk,放到 unstored bin 里。但是當(dāng)很小的 chunk 釋放的時(shí)候,ptmalloc 會(huì)把它并入 fast bin 中。同樣,某些時(shí)候,fast bin 里的連續(xù)內(nèi)存塊會(huì)被合并并加入到一個(gè) unsorted bin 里,然后再才進(jìn)入普通 bin 里。所以 malloc 小內(nèi)存的時(shí)候,是先查找 fast
bin,再查找 unsorted bin,最后查找普通的 bin,如果 unsorted bin 里的 chunk 不合適,則會(huì)把它扔到 bin 里。
大內(nèi)存分配
Ptmalloc 的分配的內(nèi)存頂部還有一個(gè) top chunk,如果前面的 bin 里的空閑 chunk 都不足以滿足需要,就是嘗試從 top chunk 里分配內(nèi)存。如果 top chunk 里也不夠,就要從操作系統(tǒng)里拿了。
還有就是特別大的內(nèi)存,會(huì)直接從系統(tǒng) mmap 出來(lái),不受 chunk 管理,這樣的內(nèi)存在回收的時(shí)候也會(huì) munmap 還給操作系統(tǒng)。
簡(jiǎn)而言之,就是:
小內(nèi)存: [獲取分配區(qū)(arena)并加鎖] -》 fast bin -》 unsorted bin -》 small bin -》 large bin
-》 top chunk -》 擴(kuò)展堆
大內(nèi)存: 直接 mmap
總結(jié)
釋放的時(shí)候,幾乎是和分配反過(guò)來(lái),再加上可一些 chunk 合并和從一個(gè) bin 轉(zhuǎn)移到另一個(gè) bin 的操作。并且如果頂部有足夠大的空閑 chunk,則收縮堆頂并還給操作系統(tǒng)。
介于此,對(duì)于 ptmalloc 的內(nèi)存分配使用有幾個(gè)注意事項(xiàng):
1. Ptmalloc 默認(rèn)后分配內(nèi)存先釋放,因?yàn)閮?nèi)存回收是從 top chunk 開(kāi)始的。
2. 避免多線程頻繁分配和釋放內(nèi)存,會(huì)造成頻繁加解鎖。
3. 不要分配長(zhǎng)生命周期的內(nèi)存塊,容易造成內(nèi)碎片,影響內(nèi)存回收。
2.5.2 Tcmalloc
具體實(shí)現(xiàn)原理不加以贅述,可自行百度學(xué)習(xí)之,總結(jié)以下特點(diǎn)。
· Tcmalloc 占用更少的額外空間。例如,分配 N 個(gè) 8 字節(jié)對(duì)象可能要使用大約 8N * 1.01
字節(jié)的空間。即,多用百分之一的空間。Ptmalloc2 使用最少 8 字節(jié)描述一個(gè) chunk。
· 更快。小對(duì)象幾乎無(wú)鎖, 》32KB 的對(duì)象從 CentralCache 中分配使用自旋鎖。 并且》32KB 對(duì)象都是頁(yè)面對(duì)齊分配,多線程的時(shí)候應(yīng)盡量避免頻繁分配,否則也會(huì)造成自旋鎖的競(jìng)爭(zhēng)和頁(yè)面對(duì)齊造成的浪費(fèi)。
2.6 思維導(dǎo)圖
3. 分析工具
從 CPU 的性能指標(biāo)出發(fā)。當(dāng)你要查看某個(gè)性能指標(biāo)時(shí),要清楚知道哪些工具可以做到。
4. 思路
性能優(yōu)化并不是沒(méi)有副作用的,通常情況下 Linux 系統(tǒng)是不需要特意調(diào)整某些指標(biāo)。往往性能優(yōu)化會(huì)帶來(lái)整體系統(tǒng)的復(fù)雜度的上升,降低了可移植性,也可能在調(diào)整某個(gè)指標(biāo)的時(shí)候?qū)е缕渌笜?biāo)異常。
并不是所有的性能問(wèn)題都需要去優(yōu)化,需要對(duì)瓶頸點(diǎn)進(jìn)行優(yōu)化。比如當(dāng)前系統(tǒng)有瓶頸,用戶 CPU 使用率升高了 10%,而系統(tǒng)系統(tǒng) CPU 使用率卻升高了 50%,這個(gè)時(shí)候就應(yīng)該首先優(yōu)化系統(tǒng) CPU 使用率。
4.1 應(yīng)用程序優(yōu)化
從應(yīng)用程序的角度來(lái)說(shuō),降低 CPU 使用率最好的方法是,排除所有不必要的工作,只保留最核心的邏輯。比如減少循環(huán)層次、減少遞歸、減少動(dòng)態(tài)內(nèi)存分配等等。
常見(jiàn)的幾種應(yīng)用程序的性能優(yōu)化方法:
· 編譯器優(yōu)化:很多編譯器都會(huì)提供優(yōu)化選項(xiàng),適當(dāng)開(kāi)啟它們,在編譯階段你就可以獲得編譯器的幫助,來(lái)提升性能。目前設(shè)備采用的優(yōu)化選項(xiàng)為 Os,相當(dāng)于 O2.5
· 算法優(yōu)化:使用異步處理,可以避免程序因?yàn)榈却硞€(gè)資源而一直阻塞,從而提升程序的并發(fā)處理能力。
· 多線程代替多進(jìn)程:線程的上下文切換并不切換進(jìn)程地址空間,因此可以降低上下文切換的成本。目前設(shè)備采用的是多線程模式。
· 使用 buffer:經(jīng)常訪問(wèn)的數(shù)據(jù),可以放在內(nèi)存中緩存起來(lái),這樣在下次用時(shí)可以直接從內(nèi)存中獲取,加快程序的處理速度。
· 小內(nèi)存使用:小內(nèi)存的申請(qǐng),在保證??臻g不溢出的情況下,盡量采用棧上申請(qǐng),少使用動(dòng)態(tài)內(nèi)存申請(qǐng),提高程序運(yùn)行效率。
4.2 系統(tǒng)優(yōu)化
從系統(tǒng)的角度來(lái)說(shuō),優(yōu)化 CPU 的運(yùn)行,一方面要充分利用 CPU 緩存的本地性,加速緩存訪問(wèn);另一方面,就是要控制進(jìn)程的 CPU 使用情況,減少進(jìn)程間的相互影響。
常見(jiàn)的方法:
· CPU 綁定:把進(jìn)程綁定到一個(gè)或者多個(gè) CPU 上,可以提高 CPU 緩存的命中率,減少跨 CPU 調(diào)度帶來(lái)的上下文切換問(wèn)題。
· 優(yōu)先級(jí)調(diào)整:使用 nice 調(diào)整進(jìn)程的優(yōu)先級(jí),正值調(diào)低優(yōu)先級(jí),負(fù)值調(diào)高優(yōu)先級(jí)。
· 中斷負(fù)載均衡:無(wú)論是軟中斷還是硬中斷,它們的中斷處理程序都可能消耗大量的 CPU。
配置 smp_affinity,就可以把中斷處理過(guò)程自動(dòng)負(fù)載均衡到其他 CPU 上
· 替換 ptmalloc:當(dāng)前使用的 gblic 庫(kù),其動(dòng)態(tài)內(nèi)存管理采
在Linux系統(tǒng)中,由于成本的限制,往往會(huì)存在資源上的不足,例如 CPU、內(nèi)存、網(wǎng)絡(luò)、IO 性能。本文,就對(duì) Linux 進(jìn)程和 CPU 的原理進(jìn)行分析,總結(jié)出 CPU 性能優(yōu)化的方法。
1. 分析手段
在理解平均負(fù)載之前,先要理清楚 Linux 下的進(jìn)程狀態(tài)。
1.1. 進(jìn)程狀態(tài)
1.1.1. R (TASK_RUNNING),可執(zhí)行狀態(tài)
只有在該狀態(tài)的進(jìn)程才可能在 CPU 上運(yùn)行。而同一時(shí)刻可能有多個(gè)進(jìn)程處于可執(zhí)行狀態(tài),這些進(jìn)程的 task_struct 結(jié)構(gòu)(進(jìn)程控制塊)被放入對(duì)應(yīng) CPU 的可執(zhí)行隊(duì)列中(一個(gè)進(jìn)程最多只能出現(xiàn)在一個(gè) CPU 的可執(zhí)行隊(duì)列中)。進(jìn)程調(diào)度器的任務(wù)就是從各個(gè) CPU 的可執(zhí)行隊(duì)列中分別選擇一個(gè)進(jìn)程在該 CPU 上運(yùn)行。
很多操作系統(tǒng)教科書(shū)將正在 CPU 上執(zhí)行的進(jìn)程定義為 RUNNING 狀態(tài)、而將可執(zhí)行但是尚未被調(diào)度執(zhí)行的進(jìn)程定義為READY狀態(tài),這兩種狀態(tài)在linux下統(tǒng)一為 TASK_RUNNING 狀態(tài)。
1.1.2. S (TASK_INTERRUPTIBLE),可中斷的睡眠狀態(tài)
處于這個(gè)狀態(tài)的進(jìn)程因?yàn)榈却衬呈录陌l(fā)生(比如等待 socket 連接、等待信號(hào)量),而被掛起。這些進(jìn)程的 task_struct 結(jié)構(gòu)被放入對(duì)應(yīng)事件的等待隊(duì)列中。當(dāng)這些事件發(fā)生時(shí)
(由外部中斷觸發(fā)、或由其他進(jìn)程觸發(fā)),對(duì)應(yīng)的等待隊(duì)列中的一個(gè)或多個(gè)進(jìn)程將被喚醒。通過(guò) ps 命令我們會(huì)看到,一般情況下,進(jìn)程列表中的絕大多數(shù)進(jìn)程都處于 TASK_INTERRUPTIBLE 狀態(tài)(除非機(jī)器的負(fù)載很高)。畢竟 CPU 就這么一兩個(gè),進(jìn)程動(dòng)輒幾十上百個(gè),如果不是絕大多數(shù)進(jìn)程都在睡眠,CPU 又怎么響應(yīng)得過(guò)來(lái)。
1.1.3. D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態(tài)
與 TASK_INTERRUPTIBLE 狀態(tài)類似,進(jìn)程處于睡眠狀態(tài),但是此刻進(jìn)程是不可中斷的。
不可中斷,指的并不是 CPU 不響應(yīng)外部硬件的中斷,而是指進(jìn)程不響應(yīng)異步信號(hào)。
絕大多數(shù)情況下,進(jìn)程處在睡眠狀態(tài)時(shí),總是應(yīng)該能夠響應(yīng)異步信號(hào)的。否則你將驚奇的發(fā)現(xiàn),kill -9 竟然殺不死一個(gè)正在睡眠的進(jìn)程了!于是我們也很好理解,為什么 ps 命令看到的進(jìn)程幾乎不會(huì)出現(xiàn) TASK_UNINTERRUPTIBLE 狀態(tài),而總是 TASK_INTERRUPTIBLE 狀態(tài)。
而 TASK_UNINTERRUPTIBLE 狀態(tài)存在的意義就在于,內(nèi)核的某些處理流程是不能被打斷的。如果響應(yīng)異步信號(hào),程序的執(zhí)行流程中就會(huì)被插入一段用于處理異步信號(hào)的流程(這個(gè)插入的流程可能只存在于內(nèi)核態(tài),也可能延伸到用戶態(tài)),于是原有的流程就被中斷了。
(參見(jiàn)《linux 內(nèi)核異步中斷淺析》) 在進(jìn)程對(duì)某些硬件進(jìn)行操作時(shí)(比如進(jìn)程調(diào)用 read 系統(tǒng)調(diào)用對(duì)某個(gè)設(shè)備文件進(jìn)行讀操作,而 read 系統(tǒng)調(diào)用最終執(zhí)行到對(duì)應(yīng)設(shè)備驅(qū)動(dòng)的代碼,并與對(duì)應(yīng)的物理設(shè)備進(jìn)行交互),可能需要使用 TASK_UNINTERRUPTIBLE 狀態(tài)對(duì)進(jìn)程進(jìn)行保護(hù),以避免進(jìn)程與設(shè)備交互的過(guò)程被打斷,造成設(shè)備陷入不可控的狀態(tài)。這種情況下的 TASK_UNINTERRUPTIBLE 狀態(tài)總是非常短暫的,通過(guò) ps 命令基本上不可能捕捉到。
1.1.4. T (TASK_STOPPED or TASK_TRACED),暫停狀態(tài)或跟蹤狀態(tài)
向進(jìn)程發(fā)送一個(gè) SIGSTOP 信號(hào),它就會(huì)因響應(yīng)該信號(hào)而進(jìn)入 TASK_STOPPED 狀態(tài)(除非該進(jìn)程本身處于 TASK_UNINTERRUPTIBLE 狀態(tài)而不響應(yīng)信號(hào))。(SIGSTOP 與 SIGKILL 信號(hào)一樣,是非常強(qiáng)制的。不允許用戶進(jìn)程通過(guò) signal 系列的系統(tǒng)調(diào)用重新設(shè)置對(duì)應(yīng)的信號(hào)處理函數(shù)。)
向進(jìn)程發(fā)送一個(gè) SIGCONT 信號(hào),可以讓其從 TASK_STOPPED 狀態(tài)恢復(fù)到 TASK_RUNNING 狀態(tài)。
當(dāng)進(jìn)程正在被跟蹤時(shí),它處于 TASK_TRACED 這個(gè)特殊的狀態(tài)?!罢诒桓櫋敝傅氖沁M(jìn)程暫停下來(lái),等待跟蹤它的進(jìn)程對(duì)它進(jìn)行操作。比如在 gdb 中對(duì)被跟蹤的進(jìn)程下一個(gè)斷點(diǎn),進(jìn)程在斷點(diǎn)處停下來(lái)的時(shí)候就處于 TASK_TRACED 狀態(tài)。而在其他時(shí)候,被跟蹤的進(jìn)程還是處于前面提到的那些狀態(tài)。
對(duì)于進(jìn)程本身來(lái)說(shuō),TASK_STOPPED 和 TASK_TRACED 狀態(tài)很類似,都是表示進(jìn)程暫停下來(lái)。而 TASK_TRACED 狀態(tài)相當(dāng)于在 TASK_STOPPED 之上多了一層保護(hù),處于 TASK_TRACED 狀態(tài)的進(jìn)程不能響應(yīng) SIGCONT 信號(hào)而被喚醒。只能等到調(diào)試進(jìn)程通過(guò) ptrace 系統(tǒng)調(diào)用執(zhí)行 PTRACE_CONT、PTRACE_DETACH 等操作(通過(guò) ptrace 系統(tǒng)調(diào)用的參數(shù)指定操作),或調(diào)試進(jìn)程退出,被調(diào)試的進(jìn)程才能恢復(fù) TASK_RUNNING 狀態(tài)。
1.1.5. Z (TASK_DEAD - EXIT_ZOMBIE),退出狀態(tài),進(jìn)程成為僵尸進(jìn)程
進(jìn)程在退出的過(guò)程中,處于 TASK_DEAD 狀態(tài)。在這個(gè)退出過(guò)程中,進(jìn)程占有的所有資源將被回收,除了 task_struct 結(jié)構(gòu)(以及少數(shù)資源)以外。于是進(jìn)程就只剩下 task_struct 這么個(gè)空殼,故稱為僵尸。之所以保留 task_struct,是因?yàn)?task_struct 里面保存了進(jìn)程的退出碼、以及一些統(tǒng)計(jì)信息。而其父進(jìn)程很可能會(huì)關(guān)心這些信息。比如在 shell 中,$?變量就保存了最后一個(gè)退出的前臺(tái)進(jìn)程的退出碼,而這個(gè)退出碼往往被作為 if 語(yǔ)句的判斷條件。
當(dāng)然,內(nèi)核也可以將這些信息保存在別的地方,而將 task_struct 結(jié)構(gòu)釋放掉,以節(jié)省一些空間。但是使用 task_struct 結(jié)構(gòu)更為方便,因?yàn)樵趦?nèi)核中已經(jīng)建立了從 pid 到 task_struct 查找關(guān)系,還有進(jìn)程間的父子關(guān)系。釋放掉 task_struct,則需要建立一些新的數(shù)據(jù)結(jié)構(gòu),以便讓父進(jìn)程找到它的子進(jìn)程的退出信息。
父進(jìn)程可以通過(guò) wait 系列的系統(tǒng)調(diào)用(如 wait4、waitid)來(lái)等待某個(gè)或某些子進(jìn)程的退出,并獲取它的退出信息。然后 wait 系列的系統(tǒng)調(diào)用會(huì)順便將子進(jìn)程的尸體(task_struct)也釋放掉。
子進(jìn)程在退出的過(guò)程中,內(nèi)核會(huì)給其父進(jìn)程發(fā)送一個(gè)信號(hào),通知父進(jìn)程來(lái)“收尸”。這個(gè)信號(hào)默認(rèn)是 SIGCHLD,但是在通過(guò) clone 系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程時(shí),可以設(shè)置這個(gè)信號(hào)。
1.2. 平均負(fù)載
單位時(shí)間內(nèi),系統(tǒng)處于可運(yùn)行狀態(tài)和不可中斷狀態(tài)的平均進(jìn)程數(shù),也就是平均活躍進(jìn)程數(shù),它和 CPU 使用率并沒(méi)有直接關(guān)系。
既然是平均的活躍進(jìn)程數(shù),那么最理想的,就是每個(gè)cpu 上都剛好運(yùn)行著一個(gè)進(jìn)程,這樣每個(gè) cpu 都得到了充分利用,比如當(dāng)平均負(fù)載 2時(shí),意味著什么呢?
1、 在只有2 個(gè) CPU的系統(tǒng)上,意味著所有的 CPU都剛好被完全占用
2、 在 4 個(gè)CPU的系統(tǒng)上,意味著 CPU 有 50%的空閑
3、 而在只有 1 個(gè)CPU 的系統(tǒng)上,則意味著有一半的進(jìn)程競(jìng)爭(zhēng)不到 CPU
1.2.1. 平均負(fù)載多少合理?
平均負(fù)載最理想的情況是等于 CPU 個(gè)數(shù)。查看系統(tǒng) CPU 的命令如下:
cat /proc/cpuinfo
Figure 1 四核 CPU 查看平均負(fù)載的命令:
給了我們?nèi)齻€(gè)不同時(shí)間間隔的平均值,給我們提供了分析系統(tǒng)負(fù)載趨勢(shì)的數(shù)據(jù)來(lái)源,讓我們更全面、更立體地理解目前的負(fù)載情況。
· 1 分鐘、5 分鐘、15 分鐘 的三個(gè)值基本相同,或者相差不大,說(shuō)明系統(tǒng)負(fù)載很平
· 如果1 分鐘的值遠(yuǎn)小于15 分鐘 的值,說(shuō)明系統(tǒng)最近 1 分鐘的負(fù)載在減少,而過(guò)去
15 分鐘內(nèi)卻有很大的負(fù)載
· 如果1 分鐘 的值遠(yuǎn)大于 15 分鐘的值,就說(shuō)明最近 1 分鐘的負(fù)載在增加。一旦 1 分鐘的平均負(fù)載接近或超過(guò)了 CPU 的個(gè)數(shù),就意味著系統(tǒng)正在發(fā)生過(guò)載的問(wèn)題。
uptime 命令在有些嵌入式設(shè)備中,會(huì)被裁減掉,但是可以通過(guò) proc 文件系統(tǒng)來(lái)獲取。命令:
cat /proc/loadavg
很顯然,當(dāng)前命令展示的平均負(fù)載在 CPU 為 4 個(gè)時(shí)候已經(jīng)過(guò)載
1.2.2. 平均負(fù)載與 CPU 使用率
平均負(fù)載不僅包括了正在使用 CPU 的進(jìn)程,還包括了等待 CPU 和等待 I/O 的進(jìn)程。
CPU 使用率是指單位時(shí)間內(nèi) CPU 繁忙情況的統(tǒng)計(jì),跟平均負(fù)載并不一定完全對(duì)應(yīng)。比如:
· CPU 密集型進(jìn)程,使用大量 CPU 會(huì)導(dǎo)致平均負(fù)載升高,此時(shí)這兩者是一致的;
· I/O 密集型進(jìn)程,等待 I/O 也會(huì)導(dǎo)致平均負(fù)載升高,但 CPU 使用率不一定很高
· 大量等待 CPU 的進(jìn)程調(diào)度也會(huì)導(dǎo)致平均負(fù)載升高,此時(shí)的 CPU 使用率也會(huì)比較高。
1.3. CPU 上下文切換
在每個(gè)任務(wù)運(yùn)行前, CPU 都需要知道任務(wù)從哪里加載、又從哪里開(kāi)始運(yùn)行、也就是說(shuō),需要系統(tǒng)事先給他設(shè)置好 CPU 寄存器和程序計(jì)數(shù)器(Program Counter, PC)
· CPU 寄存器:是 CPU 內(nèi)置的容量小、但速度極快的內(nèi)存。
· 程序計(jì)數(shù)器:是用來(lái)存儲(chǔ) CPU 正在執(zhí)行的指令位置、或者即將執(zhí)行的下一條指令位置。
它們都是 CPU 在運(yùn)行任何任務(wù)前,比如的依賴環(huán)境,因此也被叫做 CPU 上下文。
· 上下文切換:就是先把前一個(gè)任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計(jì)數(shù)器)保存起來(lái),然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置,運(yùn)行新任務(wù)。
· CPU 的上下文切換可以分為進(jìn)程上下文切換、線程上下文切換以及中斷上下文切換。
1.3.1. 進(jìn)程上下文切換
Linux 按照特權(quán)等級(jí),把進(jìn)程的運(yùn)行空間分為內(nèi)核空間和用戶空間
· 內(nèi)核空間(Ring 0)具有最高權(quán)限,可以直接訪問(wèn)所有資源。
· 用戶空間(Ring 3)只能訪問(wèn)受限資源,不能直接訪問(wèn)內(nèi)存等硬件設(shè)備,必須通過(guò)系統(tǒng)調(diào)用陷入到內(nèi)核中,才能訪問(wèn)這些特權(quán)資源。
1.3.2. 進(jìn)程上下文切換和系統(tǒng)調(diào)用的區(qū)別
進(jìn)程是由內(nèi)核來(lái)管理和調(diào)度的,進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài)。所以,進(jìn)程的上下文不僅包括了虛擬內(nèi)存、棧、全局變量等用戶空間的資源,還包括了內(nèi)核堆棧、寄存器等內(nèi)核空間的狀態(tài)。
系統(tǒng)調(diào)用過(guò)程中,并不涉及到虛擬內(nèi)存等進(jìn)程用戶態(tài)的資源,也不會(huì)切換進(jìn)程。
· 進(jìn)程上下文切換,是指從一個(gè)進(jìn)程切換到另一個(gè)進(jìn)程進(jìn)行。
· 系統(tǒng)調(diào)用過(guò)程中一直是同一個(gè)進(jìn)程在運(yùn)行。
因此,進(jìn)程的上下文切換比系統(tǒng)調(diào)用時(shí)多了一步:在保存當(dāng)前進(jìn)程的內(nèi)核狀態(tài)和 CPU 寄存器之前,需要先把該進(jìn)程的虛擬內(nèi)存、棧等保存下來(lái);而加載了下一個(gè)進(jìn)程的內(nèi)核態(tài)后,還需要刷新進(jìn)程的虛擬內(nèi)存和用戶棧。
1.3.3. 什么時(shí)候會(huì)切換進(jìn)程上文
· 進(jìn)程執(zhí)行終止,它之前使用的 CPU 會(huì)釋放出來(lái),這時(shí)再?gòu)木途w隊(duì)列里,拿一個(gè)新的進(jìn)程過(guò)來(lái)運(yùn)行。
· 當(dāng)某個(gè)進(jìn)程的時(shí)間片耗盡了,就會(huì)被系統(tǒng)掛起,切換到其他正在等待 CPU 的進(jìn)程進(jìn)行
· 進(jìn)程在系統(tǒng)資源不足(比如內(nèi)存不足)時(shí),等到資源滿足后才可以運(yùn)行,這個(gè)時(shí)候進(jìn)程也會(huì)被掛起,并由系統(tǒng)調(diào)度其他進(jìn)程運(yùn)行。
· 當(dāng)進(jìn)程通過(guò)睡眠函數(shù) sleep 這樣的方法將自己主動(dòng)掛起時(shí),自然也會(huì)重新調(diào)度。
· 當(dāng)有優(yōu)先級(jí)更高的進(jìn)程運(yùn)行時(shí),為了保證高優(yōu)先級(jí)進(jìn)程的運(yùn)行,當(dāng)前進(jìn)程會(huì)被掛起,由高優(yōu)先級(jí)進(jìn)程來(lái)運(yùn)行。
· 發(fā)生硬件中斷時(shí),CPU 上的進(jìn)程會(huì)被中斷掛起,轉(zhuǎn)而執(zhí)行內(nèi)核中的中斷程序服務(wù)。
1.3.4. 線程上下文切換
線程和進(jìn)程的區(qū)別
· 線程是調(diào)度的基本單位,而進(jìn)程則是資源擁有的基本單位。
· 當(dāng)進(jìn)程只有一個(gè)線程時(shí),可以認(rèn)為進(jìn)程就等于線程。
· 當(dāng)進(jìn)程擁有多個(gè)線程時(shí),這些線程會(huì)共享相同的虛擬內(nèi)存和全局變量等資源。這些資源在上下文切換時(shí)是不需要修改的。
· 線程也有自己的私有數(shù)據(jù),比如棧和寄存器等,這些在上下文切換時(shí)也是需要保存的。
線程的上下文切換兩種情況
· 前后兩個(gè)線程屬于不同進(jìn)程。此時(shí),因?yàn)橘Y源不共享,所以切換過(guò)程就跟進(jìn)程上下文切換是一樣的。
· 前后兩個(gè)線程屬于同一個(gè)進(jìn)程。此時(shí),因?yàn)樘摂M內(nèi)存是共享的,所以在切換時(shí),虛擬內(nèi)存這些資源就保持不動(dòng),只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)。
1.3.5. 中斷上下文切換
中斷處理會(huì)打斷進(jìn)程的正常調(diào)度和執(zhí)行。在打斷其他進(jìn)程時(shí),需要將進(jìn)程當(dāng)前的狀態(tài)保存下來(lái),中斷結(jié)束后,進(jìn)程仍然可以從原來(lái)的狀態(tài)恢復(fù)運(yùn)行。
進(jìn)程上下文切換和中斷上下文切換的區(qū)別
· 中斷上下文切換并不涉及到進(jìn)程的用戶態(tài)。所以,即便中斷過(guò)程打斷了一個(gè)正處在用戶態(tài)的進(jìn)程,也不需要保存和恢復(fù)這個(gè)進(jìn)程的虛擬內(nèi)存、全局變量等用戶態(tài)資源。中斷上下文,其實(shí)只包括內(nèi)核態(tài)中斷服務(wù)程序執(zhí)行所必須的狀態(tài),包括 CPU 寄存器、內(nèi)核堆棧、硬件中斷參數(shù)等。
· 對(duì)同一個(gè) CPU 來(lái)說(shuō),中斷處理比進(jìn)程擁有更高的優(yōu)先級(jí)。
進(jìn)程上下文切換和中斷上文切換的相同之處
· 都需要消耗 CPU,切換次數(shù)過(guò)多會(huì)耗費(fèi)大量 CPU,甚至嚴(yán)重降低系統(tǒng)的整體性能。
1.3.6. CPU 上下文切換小結(jié)
· CPU 上下文切換,是保證 Linux 系統(tǒng)正常工作的核心功能之一,一般情況下不需要我們特別關(guān)注。
· 但過(guò)多的上下文切換,會(huì)把 CPU 時(shí)間消耗在寄存器、內(nèi)核棧以及虛擬內(nèi)存等數(shù)據(jù)的保存和恢復(fù)上,從而縮短進(jìn)程真正運(yùn)行的時(shí)間,導(dǎo)致系統(tǒng)的整體性能大幅下降
1.3.7. 如何查看系統(tǒng)的上下文切換
常用的系統(tǒng)性能分析工具,主要用來(lái)分析系統(tǒng)的內(nèi)存使用情況,也常用來(lái)分析
CPU 上下文切換和中斷次數(shù)。
Figure 2 每隔 2 秒輸出一組數(shù)據(jù)
需要特別關(guān)注的四列內(nèi)容:
· cs (context switch):每秒上下文切換的次數(shù)。
· in (interrupt):每秒中斷的次數(shù)。
· r (Running or Runnable) :就緒隊(duì)列的長(zhǎng)度,也就是正在運(yùn)行和等待 CPU 的進(jìn)程數(shù)。
· b (Blocked):處在不可中斷睡眠狀態(tài)的進(jìn)程數(shù)。
在嵌入式 Linux 設(shè)備中,一般 vmstat 工具是不存在的。所以如果想要 vmstat 工具,可以自己實(shí)現(xiàn)代碼,他的原理是獲取/proc/diskstats 和/proc/slabinfo 的信息組合而成。實(shí)現(xiàn)代碼見(jiàn) procps 工具
vmstat 只給出了系統(tǒng)總體的上下文切換情況,并不能查看每個(gè)進(jìn)程的上下文切換情況。
:查看某個(gè)進(jìn)程中線程的上下文切換情況,下圖查看的是 hicore 進(jìn)程中所有的線程上下文切換情況。
關(guān)注兩列內(nèi)容:
1. 自愿上下文切換:進(jìn)程無(wú)法獲取所需資源,導(dǎo)致的上下文切換。比如, I/O、內(nèi)存等系統(tǒng)資源不足時(shí)。
2. 非自愿上下文切換:進(jìn)程由于時(shí)間片已到等原因,被系統(tǒng)強(qiáng)制調(diào)度,進(jìn)而發(fā)生的上下文切換。比如,大量進(jìn)程都在爭(zhēng)搶 CPU 時(shí)。
1.3.9. Procps 工具
procps 是一組命令行和全屏工具,是由內(nèi)核動(dòng)態(tài)生成的一個(gè) “偽” 文件系統(tǒng),可以提供進(jìn)程表中條目狀態(tài)的信息。該文件系統(tǒng)為內(nèi)核數(shù)據(jù)結(jié)構(gòu)提供了一個(gè)簡(jiǎn)易接口,procps 程序通常就集中在這個(gè)描述了系統(tǒng)進(jìn)程運(yùn)行狀態(tài)的數(shù)據(jù)結(jié)構(gòu)上。
procps 包括以下程序:
· free - 報(bào)告系統(tǒng)中可用內(nèi)存和已用內(nèi)存的數(shù)量
· kill - 基于 PID,向進(jìn)程發(fā)送信號(hào)
· pgrep - 根據(jù)名稱或其他屬性列出進(jìn)程
· pkill - 根據(jù)名稱或其他屬性向進(jìn)程發(fā)送信號(hào)
· pmap - 報(bào)告進(jìn)程的內(nèi)存映射
· ps - 報(bào)告進(jìn)程信息
· pwdx - 報(bào)告進(jìn)程的當(dāng)前目錄
· skill - pgrep/pkill 的過(guò)時(shí)版本
· slabtop - 實(shí)時(shí)顯示內(nèi)核 slab 緩存信息
· snice - Renice 一個(gè)進(jìn)程
· sysctl -運(yùn)行時(shí)內(nèi)核參數(shù)的讀或?qū)?/p>
· tload - 系統(tǒng)負(fù)載均值的可視化
· top - 正運(yùn)行進(jìn)程的實(shí)時(shí)動(dòng)態(tài)視圖
· uptime - 顯示系統(tǒng)的已運(yùn)行時(shí)間和負(fù)載情況
· vmstat - 報(bào)告虛擬內(nèi)存統(tǒng)計(jì)信息
· w - 報(bào)告登錄用戶,以及他們正在做什么
· watch - 定期執(zhí)行程序,顯示全屏輸出官網(wǎng)地址:http://procps.sourceforge.net/
1.3.10. sysstat 工具
在嵌入式 Linux 設(shè)備中同樣也不存在該工具,busybox 中也沒(méi)有相關(guān)命令。需要安裝 Linux 性能監(jiān)控工具 sysstat,他是一個(gè)工具集,包括 sar、sadf、mpstat、iostat、pidstat 等,這些工具可以監(jiān)控系統(tǒng)性能和使用情況。各工具的作用如下:
1. iostat - 提供 CPU 統(tǒng)計(jì),存儲(chǔ) I/O 統(tǒng)計(jì)(磁盤(pán)設(shè)備,分區(qū)及網(wǎng)絡(luò)文件系統(tǒng))
2. mpstat - 提供單個(gè)或組合 CPU 相關(guān)統(tǒng)計(jì)
3. pidstat - 提供 Linux 進(jìn)程級(jí)別統(tǒng)計(jì):I/O、CPU、內(nèi)存等
4. sar - 收集、報(bào)告、保存系統(tǒng)活動(dòng)信息:CPU、內(nèi)存、磁盤(pán)、中斷、網(wǎng)絡(luò)接口、TTY、內(nèi)核表等
5. sadc - 系統(tǒng)活動(dòng)數(shù)據(jù)收集器,作為 sar 后端使用
6. sa1 - 收集系統(tǒng)活動(dòng)日常數(shù)據(jù),并二進(jìn)制格式存儲(chǔ),它作為 sadc 的工具的前端,可以通過(guò) cron 來(lái)調(diào)用
7. sa2 - 生成系統(tǒng)每日活動(dòng)報(bào)告,同樣可作為 sadc 的工具的前端,可以通過(guò) cron 來(lái)調(diào)用
8. sadf - 可以以 CSV、XML 格式等顯示 sar 收集的性能數(shù)據(jù),這樣非常方便的將系統(tǒng)數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫(kù)中,或?qū)氲?Excel 中來(lái)生成圖表
9. nfsiostat-sysstat: 提供 NFS I/O 統(tǒng)計(jì)
10. cifsiostat: 提供 CIFS 統(tǒng)計(jì)
sysstat 功能強(qiáng)大,功能也在不斷的增強(qiáng),每個(gè)版本提供了不同的功能,可以到 sysstat 官網(wǎng)了 解 工 具 最 先 發(fā) 展 情 況 和 獲 得 相 應(yīng) 的 幫 助 手 冊(cè) 。 官 網(wǎng) 地 址 :
1.3.11. 中斷
中斷是一種異步的事件處理機(jī)制,可以提高系統(tǒng)的并發(fā)處理能力。中斷處理程序會(huì)打斷其他進(jìn)程的運(yùn)行,為了減少對(duì)正常進(jìn)程運(yùn)行調(diào)度的影響,中斷處理程序就需要盡可能快地運(yùn)行。
Linux 將中斷處理過(guò)程分成了兩個(gè)階段,也就是上半部和下半部。
· 上半部用來(lái)快速處理中斷,它在中斷禁止模式下運(yùn)行,主要處理跟硬件緊密相關(guān)的或時(shí)間敏感的工作。
· 下半部用來(lái)延時(shí)處理上半部未完成的工作,通常以內(nèi)核線程的方式運(yùn)行。
/proc/interrupts:查看硬中斷發(fā)生的類型
硬件中斷發(fā)生頻繁,是件很消耗 CPU 資源的事情,Linux 默認(rèn)情況下是將所有的硬件中斷都綁定在 CPU0 上,在多核 CPU 條件下如果有辦法把大量硬件中斷分配給不同的
CPU (core) 處理顯然能很好的平衡性能。
1.3.13. 根據(jù)上下文切換的類型做具體分析
· 自愿上下文切換變多,說(shuō)明進(jìn)程都在等待資源,有可能發(fā)生 I/O 等其他問(wèn)題
· 非自愿上下文切換變多,說(shuō)明進(jìn)程都在被強(qiáng)制調(diào)度,也就是都在爭(zhēng)搶 CPU,說(shuō)明 CPU 的確成了瓶頸
· 中斷次數(shù)變多,說(shuō)明 CPU 被中斷處理程序占用,還需要通過(guò)查看 /proc/interrupts 文件來(lái)分析具體的中斷類型。
1.4. CPU 使用率
/proc/stat,提供的是系統(tǒng)的 CPU 和任務(wù)統(tǒng)計(jì)信息。這個(gè)信息非常的原始
CPU 使用率相關(guān)的重要指標(biāo)
· 第一列:user(us),代表用戶態(tài) CPU 時(shí)間。
· 第二列:nice(ni),代表低優(yōu)先級(jí)用戶態(tài) CPU 時(shí)間,也就是進(jìn)程的 nice 值被調(diào)整為 1-
19 之間時(shí)的 CPU 時(shí)間。nice 可取值范圍是 -20 到 19, 數(shù)值越大,優(yōu)先級(jí)反而越低
· 第三列:system (sys),代表內(nèi)核態(tài) CPU 時(shí)間。
· 第四列:idle(us),代表空閑時(shí)間。注意,這里它不包括等待 I/O 的時(shí)間(iowait)。
· 第五列:iowait(wa),代表等待 I/O 的 CPU 時(shí)間。
· 第六列:irq(hi),代表處理硬中斷的 CPU 時(shí)間
· 第七列:softirq(si),代表處理軟中斷的 CPU 時(shí)間。
真正查看 CPU 使用率的命令是通過(guò) top 命令
1.5. 軟中斷
提供了軟中斷的運(yùn)行情況。
· 注意軟中斷的類型,也就是第一列內(nèi)容。
· 注意同一種軟中斷在不同 CPU 上的分布情況,也就是同一行內(nèi)容。
· 軟中斷實(shí)際上是以內(nèi)核線程的方式運(yùn)行的,每個(gè) CPU 都對(duì)應(yīng)一個(gè)軟中斷內(nèi)核線程,這個(gè)軟中斷內(nèi)核線程就叫做 ksoftirqd/CPU 編號(hào)
2. 優(yōu)化方法
2.1 CPU 使用率
CPU 使用率描述了非空閑時(shí)間占總 CPU 時(shí)間的百分比,根據(jù) CPU 上運(yùn)行任務(wù)的不同,又被分為用戶 CPU、系統(tǒng) CPU、等待 I/O CPU、軟中斷和硬中斷等。
· 用戶 CPU 使用率,包括用戶態(tài) CPU 使用率(user) 和低優(yōu)先級(jí)用戶態(tài) CPU 使用率 (nice),表示 CPU 在用戶態(tài)運(yùn)行的時(shí)間百分比。用戶 CPU 使用率高,通常說(shuō)明有應(yīng)用程序比較繁忙。
· 系統(tǒng) CPU 使用率,表示 CPU 在內(nèi)核態(tài)運(yùn)行的時(shí)間百分比(不包括中斷)。系統(tǒng) CPU 使用率高,說(shuō)明內(nèi)核比較繁忙。
· 等待 I/O 的 CPU 使用率,通常也稱為 iowait,表示等待 I/O 的時(shí)間百分比。iowait 高,通常說(shuō)明系統(tǒng)與硬件設(shè)備的 I/O 交互時(shí)間比較長(zhǎng)。
· 軟中斷和硬中斷的 CPU 使用率,分別表示內(nèi)核調(diào)用軟中斷處理程序、硬中斷處理程序的時(shí)間百分比。它們的使用率高,通常說(shuō)明系統(tǒng)發(fā)生了大量的中斷。
2.2 平均負(fù)載(Load Average)
平均負(fù)載,也就是系統(tǒng)的平均活躍進(jìn)程數(shù),它反映了系統(tǒng)的整體負(fù)載情況,主要包括三個(gè)數(shù)值,分別指過(guò)去 1 分鐘、過(guò)去 5 分鐘和過(guò)去 15 分鐘的平均復(fù)制子。
理想情況下,平均負(fù)載等于邏輯 CPU 個(gè)數(shù),這表示每個(gè) CPU 都恰好被充分利用。如果平均負(fù)載大于邏輯 CPU 個(gè)數(shù),就表示負(fù)載比較重了。
2.3 進(jìn)程上下文切換
· 無(wú)法獲取資源而導(dǎo)致的自愿上下文切換。
· 被系統(tǒng)強(qiáng)制調(diào)度導(dǎo)致的非自愿上下文切換。
2.4 CPU 緩存的命中率
由于 CPU 發(fā)展的速度遠(yuǎn)快于內(nèi)存的發(fā)展, CPU 的處理速度就比內(nèi)存的訪問(wèn)速度快得多。這樣,CPU 在訪問(wèn)內(nèi)存的時(shí)候,免不了要等待內(nèi)存的響應(yīng)。為了協(xié)調(diào)這兩者巨大的性能差距,CPU 緩存(通常是多級(jí)緩存)就出現(xiàn)了。
根據(jù)不斷增長(zhǎng)的熱點(diǎn)數(shù)據(jù),這些緩存按照大小不同分為 L1、L2、L3 等三級(jí)緩存,其中
L1 和 L2 常用在單核中,L3 則用在多核中。
從 L1 到 L3,三級(jí)緩存的大小依次增大,相應(yīng)的,性能依次降低(當(dāng)然比內(nèi)存還是好
得多)。而它們的命中率,衡量的是 CPU 緩存的復(fù)用情況,命中率越高,則表示性能越好。
2.5 tcmalloc 替換 ptmalloc
2.5.1 ptmalloc
Ptmalloc 采用主-從分配區(qū)的模式,當(dāng)一個(gè)線程需要分配資源的時(shí)候,從鏈表中找到一個(gè)沒(méi)加鎖的分配區(qū),在進(jìn)行內(nèi)存分配。
小內(nèi)存分配
在 ptmalloc 內(nèi)部,內(nèi)存塊采用 chunk 管理,并且將大小相似的 chunk 用鏈表管理,一個(gè)鏈表被稱為一個(gè) bin。前 64 個(gè) bin 里,相鄰的 bin 內(nèi)的 chunk 大小相差 8 字節(jié),稱為 small bin,后面的是 large bin,large bin 里的 chunk 按先大小,再最近使用的順序排列,每次分配都找一個(gè)最小的能夠使用的 chunk。
Chunk 的結(jié)構(gòu)如上所示,A 位表示是不是在主分配區(qū),M 表示是不是 mmap 出來(lái)的,P 表示上一個(gè)內(nèi)存緊鄰的 chunk 是否在使用,如果沒(méi)在使用,則 size of previous
chunk 是上一個(gè) chunk 的大小,否則無(wú)意義(而且被用作被分配出去的內(nèi)存了),正式根據(jù)
P 標(biāo)記位和 size of previous chunk 在 free 內(nèi)存塊的時(shí)候來(lái)進(jìn)行 chunk 合并的。當(dāng)然,如果 chunk 空閑,mem 里還記錄了一些指針用于索引臨近大小的 chunk 的,實(shí)現(xiàn)原理就不復(fù)述了,知道大致作用就行。
在 free 的時(shí)候,ptmalloc 會(huì)檢查附近的 chunk,并嘗試把連續(xù)空閑的 chunk 合并成一個(gè)大的 chunk,放到 unstored bin 里。但是當(dāng)很小的 chunk 釋放的時(shí)候,ptmalloc 會(huì)把它并入 fast bin 中。同樣,某些時(shí)候,fast bin 里的連續(xù)內(nèi)存塊會(huì)被合并并加入到一個(gè) unsorted bin 里,然后再才進(jìn)入普通 bin 里。所以 malloc 小內(nèi)存的時(shí)候,是先查找 fast
bin,再查找 unsorted bin,最后查找普通的 bin,如果 unsorted bin 里的 chunk 不合適,則會(huì)把它扔到 bin 里。
大內(nèi)存分配
Ptmalloc 的分配的內(nèi)存頂部還有一個(gè) top chunk,如果前面的 bin 里的空閑 chunk 都不足以滿足需要,就是嘗試從 top chunk 里分配內(nèi)存。如果 top chunk 里也不夠,就要從操作系統(tǒng)里拿了。
還有就是特別大的內(nèi)存,會(huì)直接從系統(tǒng) mmap 出來(lái),不受 chunk 管理,這樣的內(nèi)存在回收的時(shí)候也會(huì) munmap 還給操作系統(tǒng)。
簡(jiǎn)而言之,就是:
小內(nèi)存: [獲取分配區(qū)(arena)并加鎖] -》 fast bin -》 unsorted bin -》 small bin -》 large bin
-》 top chunk -》 擴(kuò)展堆
大內(nèi)存: 直接 mmap
總結(jié)
釋放的時(shí)候,幾乎是和分配反過(guò)來(lái),再加上可一些 chunk 合并和從一個(gè) bin 轉(zhuǎn)移到另一個(gè) bin 的操作。并且如果頂部有足夠大的空閑 chunk,則收縮堆頂并還給操作系統(tǒng)。
介于此,對(duì)于 ptmalloc 的內(nèi)存分配使用有幾個(gè)注意事項(xiàng):
1. Ptmalloc 默認(rèn)后分配內(nèi)存先釋放,因?yàn)閮?nèi)存回收是從 top chunk 開(kāi)始的。
2. 避免多線程頻繁分配和釋放內(nèi)存,會(huì)造成頻繁加解鎖。
3. 不要分配長(zhǎng)生命周期的內(nèi)存塊,容易造成內(nèi)碎片,影響內(nèi)存回收。
2.5.2 Tcmalloc
具體實(shí)現(xiàn)原理不加以贅述,可自行百度學(xué)習(xí)之,總結(jié)以下特點(diǎn)。
· Tcmalloc 占用更少的額外空間。例如,分配 N 個(gè) 8 字節(jié)對(duì)象可能要使用大約 8N * 1.01
字節(jié)的空間。即,多用百分之一的空間。Ptmalloc2 使用最少 8 字節(jié)描述一個(gè) chunk。
· 更快。小對(duì)象幾乎無(wú)鎖, 》32KB 的對(duì)象從 CentralCache 中分配使用自旋鎖。 并且》32KB 對(duì)象都是頁(yè)面對(duì)齊分配,多線程的時(shí)候應(yīng)盡量避免頻繁分配,否則也會(huì)造成自旋鎖的競(jìng)爭(zhēng)和頁(yè)面對(duì)齊造成的浪費(fèi)。
2.6 思維導(dǎo)圖
3. 分析工具
從 CPU 的性能指標(biāo)出發(fā)。當(dāng)你要查看某個(gè)性能指標(biāo)時(shí),要清楚知道哪些工具可以做到。
4. 思路
性能優(yōu)化并不是沒(méi)有副作用的,通常情況下 Linux 系統(tǒng)是不需要特意調(diào)整某些指標(biāo)。往往性能優(yōu)化會(huì)帶來(lái)整體系統(tǒng)的復(fù)雜度的上升,降低了可移植性,也可能在調(diào)整某個(gè)指標(biāo)的時(shí)候?qū)е缕渌笜?biāo)異常。
并不是所有的性能問(wèn)題都需要去優(yōu)化,需要對(duì)瓶頸點(diǎn)進(jìn)行優(yōu)化。比如當(dāng)前系統(tǒng)有瓶頸,用戶 CPU 使用率升高了 10%,而系統(tǒng)系統(tǒng) CPU 使用率卻升高了 50%,這個(gè)時(shí)候就應(yīng)該首先優(yōu)化系統(tǒng) CPU 使用率。
4.1 應(yīng)用程序優(yōu)化
從應(yīng)用程序的角度來(lái)說(shuō),降低 CPU 使用率最好的方法是,排除所有不必要的工作,只保留最核心的邏輯。比如減少循環(huán)層次、減少遞歸、減少動(dòng)態(tài)內(nèi)存分配等等。
常見(jiàn)的幾種應(yīng)用程序的性能優(yōu)化方法:
· 編譯器優(yōu)化:很多編譯器都會(huì)提供優(yōu)化選項(xiàng),適當(dāng)開(kāi)啟它們,在編譯階段你就可以獲得編譯器的幫助,來(lái)提升性能。目前設(shè)備采用的優(yōu)化選項(xiàng)為 Os,相當(dāng)于 O2.5
· 算法優(yōu)化:使用異步處理,可以避免程序因?yàn)榈却硞€(gè)資源而一直阻塞,從而提升程序的并發(fā)處理能力。
· 多線程代替多進(jìn)程:線程的上下文切換并不切換進(jìn)程地址空間,因此可以降低上下文切換的成本。目前設(shè)備采用的是多線程模式。
· 使用 buffer:經(jīng)常訪問(wèn)的數(shù)據(jù),可以放在內(nèi)存中緩存起來(lái),這樣在下次用時(shí)可以直接從內(nèi)存中獲取,加快程序的處理速度。
· 小內(nèi)存使用:小內(nèi)存的申請(qǐng),在保證棧空間不溢出的情況下,盡量采用棧上申請(qǐng),少使用動(dòng)態(tài)內(nèi)存申請(qǐng),提高程序運(yùn)行效率。
4.2 系統(tǒng)優(yōu)化
從系統(tǒng)的角度來(lái)說(shuō),優(yōu)化 CPU 的運(yùn)行,一方面要充分利用 CPU 緩存的本地性,加速緩存訪問(wèn);另一方面,就是要控制進(jìn)程的 CPU 使用情況,減少進(jìn)程間的相互影響。
常見(jiàn)的方法:
· CPU 綁定:把進(jìn)程綁定到一個(gè)或者多個(gè) CPU 上,可以提高 CPU 緩存的命中率,減少跨 CPU 調(diào)度帶來(lái)的上下文切換問(wèn)題。
· 優(yōu)先級(jí)調(diào)整:使用 nice 調(diào)整進(jìn)程的優(yōu)先級(jí),正值調(diào)低優(yōu)先級(jí),負(fù)值調(diào)高優(yōu)先級(jí)。
· 中斷負(fù)載均衡:無(wú)論是軟中斷還是硬中斷,它們的中斷處理程序都可能消耗大量的 CPU。
配置 smp_affinity,就可以把中斷處理過(guò)程自動(dòng)負(fù)載均衡到其他 CPU 上
· 替換 ptmalloc:當(dāng)前使用的 gblic 庫(kù),其動(dòng)態(tài)內(nèi)存管理采用就是 ptmalloc。當(dāng)前應(yīng)用程序大量使用小尺寸動(dòng)態(tài)內(nèi)存,可以采用 tcmalloc,在小內(nèi)存上更快,幾乎無(wú)鎖。
用就是 ptmalloc。當(dāng)前應(yīng)用程序大量使用小尺寸動(dòng)態(tài)內(nèi)存,可以采用 tcmalloc,在小內(nèi)存上更快,幾乎無(wú)鎖。
-
cpu
+關(guān)注
關(guān)注
68文章
10854瀏覽量
211569 -
Linux
+關(guān)注
關(guān)注
87文章
11292瀏覽量
209318 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3019瀏覽量
74001
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論