簡析 Linux 的 CPU 時間

從 CPU 時間說起...下面這個是 top 命令的界面,相信大家應該都不陌生 。
top - 19:01:38 up 91 days, 23:06,1 user,load average: 0.00, 0.01, 0.05Tasks: 151 total,1 running, 149 sleeping,1 stopped,0 zombie%Cpu(s):0.0 us,0.1 sy,0.0 ni, 99.8 id,0.0 wa,0.0 hi,0.0 si,0.0 stKiB Mem :8010420 total,5803596 free,341300 used,1865524 buff/cacheKiB Swap:0 total,0 free,0 used.6954384 avail MemPID USERPRNIVIRTRESSHR S%CPU %MEMTIME+ COMMAND13436 root200 1382776280405728 S0.30.4 251:21.06 n9e-collector1 root2004318433842212 S0.00.05:15.64 systemd2 root200000 S0.00.00:00.28 kthreadd3 root200000 S0.00.00:00.58 ksoftirqd/05 root0 -20000 S0.00.00:00.00 kworker/0:0H7 rootrt0000 S0.00.00:35.48 migration/0%Cpu(s): 這一行表示的是 CPU 不同時間的占比,其中大家比較熟悉的應該是 system timeuser time

  • 正常情況下 user time 占比應該最高,這是進程運行應用代碼的的時間占比(CPU 密集)
  • system time 占用率高,則意味著存在頻繁的系統調用(IO 密集)或者一些潛在的性能問題
不熟悉的朋友可以參考下面這張圖(來源于極客時間的課程):
簡析 Linux 的 CPU 時間

文章插圖
接下來我們將探究隱藏在這些時間背后的操作原理 。
內核態與用戶態【簡析 Linux 的 CPU 時間】操作系統的核心功能就是管理硬件資源,因此不可避免會使用到一些直接操作硬件的CPU指令,這類指令我們稱之為特權指令 。特權指令如果使用不當,將會導致整個系統的崩潰,因此操作系統提供了一組特殊的資源訪問代碼 —— 內核kernel 來負責執行這些指令 。
操作系統將虛擬地址空間劃分為兩部分:
  • 內核空間kernel memotry:存放內核代碼和數據(進程間共享)
  • 用戶空間user memotry:存放用戶程序的代碼和數據(相互隔離)

簡析 Linux 的 CPU 時間

文章插圖
通過區分內核空間和用戶空間的設計,隔離了操作系統代碼與應用程序代碼 。即便是單個應用程序出現錯誤也不會影響到操作系統的穩定性,這樣其它的程序還可以正常的運行 。
應用程序通過內核提供的接口,訪問 CPU、內存、I/O 等硬件資源,我們將該過程稱為系統調用system call 。系統調用是操作系統的最小功能單位 。
每個進程處于活動狀態時 , 可能處于以下兩種狀態之一:
  • 執行用戶空間的代碼時 , 處于用戶態
  • 執行內核空間的代碼時(系統調用) , 處于內核態
每次執行系統調用時,都需要經歷以下變化:
  • CPU 保存用戶態指令,切換為內核態
  • 在內核態下訪問系統資源
  • CPU 恢復用戶態指令 , 切換回用戶態
而之前的 user timesystem time 分別就是對應 CPU 在用戶態與內核態的運行時間 。
上下文切換當發生以下狀況時 , 線程會被掛起,并由系統調度其他線程運行:
  • 等待系統資源分配
  • 調用sleep主動掛起
  • 被優先級更高的線程搶占
  • 發生硬件中斷,跳轉執行內核的中斷服務程序
同個進程下的線程共享進程的用戶態空間,因此當同個進程的線程發生切換時,都需要經歷以下變化:
  • CPU 保存線程 A 用戶態指令,切換為內核態
  • 保存線程 A 私有資源(棧、寄存器...)/li>
  • 加載線程 B 私有資源(棧、寄存器...)
  • CPU 恢復線程 B 用戶態指令,切換回用戶態
不同線程的用戶態空間資源是相互隔離的,當不同進程的線程發生切換時,都需要經歷以下變化:
  • CPU 保存線程 A 用戶態指令,切換為內核態
  • 保存線程 A 私有資源(棧、寄存器...)
  • 保存線程 A 用戶態資源(虛擬內存、全局變量...)
  • 加載線程 B 用戶態資源(虛擬內存、全局變量...)
  • 加載線程 B 私有資源(棧、寄存器...)
  • CPU 恢復線程 B 用戶態指令,切換回用戶態
每次保存和恢復上下文的過程,都是在系統態進行的 , 并且需要幾十納秒到數微秒的 CPU 時間 。當切換次數較多時會耗費大量的 system time , 進而大大縮短了真正運行進程的 user time 。
當用戶線程過多時 , 會引起大量的上下文切換,導致不必要的性能開銷 。
線程調度Linux 中的線程是從父進程 fork 出的輕量進程,它們共享父進程的內存空間 。

推薦閱讀