CPU性能篇-系統中出現大量不可中斷進程和僵尸進程怎么辦? Day 05

在上下文切換的文章中,學習并分析了系統 CPU 使用率高的問題,剩下的等待 I/O 的 CPU 使用率(以下簡稱為 iowait)升高,也是最常見的一個服務器性能問題。今天就來看一個多進程 I/O 的案例,并分析這種情況。

1. 進程狀態

當 iowait 升高時,進程很可能因為得不到硬件的響應,而長時間處于不可中斷狀態。從 ps 或者 top 命令的輸出中,可以發現它們都處于 D 狀態,也就是不可中斷狀態(Uninterruptible Sleep)。

1.1 進程狀態介紹

top 和 ps 是最常用的查看進程狀態的工具。

$ topPID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
28961 root      20   0   43816   3148   4040 R   3.2  0.0   0:00.01 top620 root      20   0   37280  33676    908 D   0.3  0.4   0:00.01 app1 root      20   0  160072   9416   6752 S   0.0  0.1   0:37.64 systemd1896 root      20   0       0      0      0 Z   0.0  0.0   0:00.00 devapp2 root      20   0       0      0      0 S   0.0  0.0   0:00.10 kthreadd4 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 kworker/0:0H6 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 mm_percpu_wq7 root      20   0       0      0      0 S   0.0  0.0   0:06.37 ksoftirqd/0
  • R:是 Running 或 Runnable 的縮寫,表示進程在 CPU 的就緒隊列中,正在運行或者正在等待運行。
  • D:是 Disk Sleep 的縮寫,也就是不可中斷狀態睡眠(Uninterruptible Sleep),一般表示進程正在跟硬件交互,并且交互過程不允許被其他進程或中斷打斷。
  • Z:是 Zombie 的縮寫,它表示僵尸進程,也就是進程實際上已經結束了,但是父進程還沒有回收它的資源(比如進程的描述符、PID 等)。
  • S:是 Interruptible Sleep 的縮寫,也就是可中斷狀態睡眠,表示進程因為等待某個事件而被系統掛起。當進程等待的事件發生時,它會被喚醒并進入 R 狀態。
  • I:是 Idle 的縮寫,也就是空閑狀態,用在不可中斷睡眠的內核線程上。前面說了,硬件交互導致的不可中斷進程用 D 表示,但對某些內核線程來說,它們有可能實際上并沒有任何負載,用 Idle 正是為了區分這種情況。要注意,D 狀態的進程會導致平均負載升高, I 狀態的進程卻不會。
  • T或者t:Stopped 或 Traced 的縮寫,表示進程處于暫停或者跟蹤狀態。向一個進程發送 SIGSTOP 信號,它就會因響應這個信號變成暫停狀態(Stopped);再向它發送 SIGCONT 信號,進程又會恢復運行(如果進程是終端里直接啟動的,則需要你用 fg 命令,恢復到前臺運行)。
  • X:是 Dead 的縮寫,表示進程已經消亡,所以你不會在 top 或者 ps 命令中看到它。

1.1.1 D 不可中斷狀態

該狀態是為了保證進程數據與硬件狀態一致,而且分成兩個場景

  • 正常場景:
    • 不可中斷狀態在很短時間內就會結束。所以,短時的不可中斷狀態進程,我們一般可以忽略。
  • 異常場景
    • 系統或硬件發生了故障,進程可能會在不可中斷狀態保持很久,甚至導致系統中出現大量不可中斷進程。這時,就得注意下,系統是不是出現了 I/O 等性能問題。

1.1.2 Z 僵尸進程

這是多進程應用很容易碰到的問題。主要也分為兩個場景

  • 正常場景
    • 當一個進程創建了子進程后,它應該通過系統調用 wait() 或者 waitpid() 等待子進程結束,回收子進程的資源;而子進程在結束時,會向它的父進程發送 SIGCHLD 信號,所以,父進程還可以注冊 SIGCHLD 信號的處理函數,異步回收資源。
  • 異常場景
    • 如果父進程沒這么做,或是子進程執行太快,父進程還沒來得及處理子進程狀態,子進程就已經提前退出,那這時的子進程就會變成僵尸進程。換句話說,父親應該一直對兒子負責,善始善終,如果不作為或者跟不上,都會導致“問題少年”的出現。

通常,僵尸進程持續的時間都比較短,在父進程回收它的資源后就會消亡;或者在父進程退出后,由 init 進程回收后也會消亡。

僵尸進程的危害:大量的僵尸進程會用盡 PID 進程號,導致新進程不能創建,所以這種情況一定要避免。

2. 案例分析:多進程應用場景

2.1 環境準備

  • 機器配置:4 CPU,8GB 內存,Ubuntu 22.04.5,1臺機器。
  • 預先安裝 docker、sysstat、dstat 等工具。dstat 是一個新的性能工具,它吸收了 vmstat、iostat、ifstat 等幾種工具的優點,可以同時觀察系統的 CPU、磁盤 I/O、網絡以及內存使用情況。

安裝過程:略

2.2 部署測試應用

注意:這里部署完了一定要立刻進行接下來的步驟,不然立刻關閉這個容器,否則時間長了會把系統的負載打滿,導致無法操作。

root@yunwei-virtual-machine:~# docker run --privileged --name=app -itd feisky/app:iowait
27b316cda218482c9870ad54908ec4a4f4daf0ec52d8b488e064c1530b617d74
root@yunwei-virtual-machine:~# docker ps -l
CONTAINER ID   IMAGE               COMMAND   CREATED          STATUS         PORTS     NAMES
27b316cda218   feisky/app:iowait   "/app"    20 seconds ago   Up 5 seconds             app

然后通過ps命令查看應用是否部署成功,如下圖就表示部署成功了:

從上圖,可以發現多個 app 進程已經啟動,并且它們的狀態分別是 Ss+、D+、S+。其中,S 表示可中斷睡眠狀態,D 表示不可中斷睡眠狀態,我們在前面剛學過,那后面的 s 和 + 是什么意思呢?s 表示這個進程是一個會話的領導進程,而 + 表示前臺進程組。

2.3 什么是進程組和會話

  • 進程組表示一組相互關聯的進程,比如每個子進程都是父進程所在組的成員;
  • 會話是指共享同一個控制終端的一個或多個進程組。

比如,我們通過 SSH 登錄服務器,就會打開一個控制終端(TTY),這個控制終端就對應一個會話。而我們在終端中運行的命令以及它們的子進程,就構成了一個個的進程組,其中,在后臺運行的命令,構成后臺進程組;在前臺運行的命令,構成前臺進程組。

2.4 查看系統資源使用情況

這里匯總一下,上圖的問題:

  • 第一行 load average: 4.59, 2.10, 1.35。
    • 光是1分鐘的負載,就已經打滿了(機器4C),這個時候系統的性能很明顯已經受到的影響。而且隨著時間推移,負載還在不斷上升。
  • 第二行?有1個正在運行的進程,但是有12個僵尸進程,這個僵尸進程也是隨著時間的推移在不斷增多。
  • 第三四五六行?用戶態和內核態的cpu使用率都很低,但是iowait有3個CPU>95%,一個73.8,這很明顯不正常。
  • 進程狀態:CPU使用率最高的進程也只占用了1%,但有好幾個進程處于 D 狀態,它們可能在等待 I/O,但光憑這里并不能確定是它們導致了 iowait 升高。

那么現狀總結下來就是:

  1. iowait 太高了,導致系統的平均負載升高,甚至超過了系統 CPU 的個數。
  2. 僵尸進程在不斷增多,說明有程序沒能正確清理子進程的資源。

2.5 iowait分析

推薦使用dstat ,它的好處是可以同時查看 CPU 和 I/O 這兩種資源的使用情況,便于對比分析。

root@yunwei-virtual-machine:~# dstat 1 10 # 間隔1秒輸出10組數據
You did not select any stats, using -cdngy by default.
--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read  writ| recv  send|  in   out | int   csw0   1  85  13   0|  57M   18k|   0     0 |   0     0 | 320   3670   3   0  97   0| 411M    0 | 606B  338B|   0     0 |1172  16420   2  10  88   0| 499M    0 | 846B  248B|   0     0 |1212  19030   2   0  98   0| 319M 4096B| 858B  330B|   0     0 | 913  13351   3   0  96   0| 372M 4096B| 786B  162B|   0     0 |1109  14631   7   0  92   0| 472M   32k| 546B  162B|   0     0 |1469  18190   3   0  97   0| 435M    0 | 546B  162B|   0     0 |1218  17380   4   4  92   0| 568M    0 | 666B  146B|   0     0 |1481  21200   2   0  97   0| 348M    0 | 906B  383B|   0     0 |1021  14000   4   6  89   0| 471M    0 | 846B  146B|   0     0 |1339  1843

從 dstat 的輸出,我們可以看到,每當 iowait 升高(wa)時,磁盤的讀請求(read)都會很大。這說明 iowait 的升高跟磁盤的讀請求有關,很可能就是磁盤讀導致的。

2.6 查找導致iowait升高的進程

繼續在剛才的終端中,運行 top 命令,觀察 D 狀態的進程:

觀察一會兒按 Ctrl+C 結束

然后隨便找一個D狀態進程的PID,使用pidstat -d來分析io使用情況:

root@yunwei-virtual-machine:~# pidstat -d -p 22540 1 3 # 每隔一秒輸出3組數據
Linux 6.8.0-60-generic (yunwei-virtual-machine)         2025年06月18日  _x86_64_        (4 CPU)16時51分57秒   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
16時51分58秒     0     22540  65536.00      0.00      0.00       0  app
16時51分59秒     0     22540      0.00      0.00      0.00       0  app
16時52分00秒     0     22540  65536.00      0.00      0.00       0  app
Average:        0     22540  43690.67      0.00      0.00       0  app

通過結果可以發現,就是這個app進程在讀取磁盤,并且每秒最多有64m的讀取。

2.7 查找進程到底在執行什么樣的IO操作

回顧一下進程用戶態和內核態的區別:

  • 進程想要訪問磁盤,就必須使用系統調用,所以接下來,重點就是找出 app 進程的系統調用了。

strace 正是最常用的跟蹤進程系統調用的工具。所以,我們從 pidstat 的輸出中拿到進程的 PID 號,比如 6082,然后在終端中運行 strace 命令,并用 -p 參數指定 PID 號:

$ strace -p 6082
strace: attach: ptrace(PTRACE_SEIZE, 6082): Operation not permitted

這里居然報錯了,顯示“不允許操作!”。

$ ps aux | grep 6082
root      6082  0.0  0.0      0     0 pts/0    Z+   13:43   0:00 [app] <defunct>

然后通過ps查看進程,發現進程已經變成了僵尸進程(Z),僵尸進程是已經退出運行的進程,是沒有辦法分析它的系統調用的。

但此時系統的iowait依然還是處于一個異常狀態,而使用top、pidstat這類的工具,也無法查出更多有用的信息,怎么辦?使用基于事件記錄的動態追蹤工具。

2.7.1 使用perf查看調用棧信息

root@yunwei-virtual-machine:/tmp# perf record -g # 生成調用用棧文件,約15s后CTRL C退出。
root@yunwei-virtual-machine:/tmp# ll -rth
-rw-------  1 root   root    61M  6月 26 16:33 perf.dataroot@yunwei-virtual-machine:/tmp# perf report # 查看調用棧

接著,找到我們關注的 app 進程,按回車鍵展開調用棧,你就會得到下面這張調用關系圖:

這個圖里的 swapper 是內核中的調度進程,可以先忽略掉。

可以發現, app 的確在通過系統調用 sys_read() 讀取數據。并且從 new_sync_read 和 blkdev_direct_IO 能看出,進程正在對磁盤進行直接讀,也就是繞過了系統緩存,每個讀請求都會從磁盤直接讀,這就可以解釋我們觀察到的 iowait 升高了。

看來,罪魁禍首是 app 內部進行了磁盤的直接 I/O 啊!

下面的問題就容易解決了。我們接下來應該從代碼層面分析,究竟是哪里出現了直接讀請求。查看源碼文件 app.c,你會發現它果然使用了 O_DIRECT 選項打開磁盤,于是繞過了系統緩存,直接對磁盤進行讀寫。

open(disk, O_RDONLY|O_DIRECT|O_LARGEFILE, 0755)

直接讀寫磁盤,對 I/O 敏感型應用(比如數據庫系統)是很友好的,因為你可以在應用中,直接控制磁盤的讀寫。但在大部分情況下,我們最好還是通過系統緩存來優化磁盤 I/O,換句話說,刪除 O_DIRECT 這個選項就是了。

3. 僵尸進程

僵尸進程是因為父進程沒有回收子進程的資源而出現的,那么,要解決掉它們,就要找到它們的根兒,也就是找出父進程,然后在父進程里解決。

3.1 使用pstree查看進程關系

# -a 表示輸出命令行選項
# p表PID
# s表示指定進程的父進程
$ pstree -aps 3084
systemd,1└─dockerd,15006 -H fd://└─docker-containe,15024 --config /var/run/docker/containerd/containerd.toml└─docker-containe,3991 -namespace moby -workdir...└─app,4009└─(app,3084)

運行完,你會發現 3084 號進程的父進程是 4009,也就是 app 應用。這種怎么辦呢?查看 app 應用程序的代碼,看看子進程結束的處理是否正確,比如有沒有調用 wait() 或 waitpid() ,抑或是,有沒有注冊 SIGCHLD 信號的處理函數。找到并修復就好了。

4. 小結

iowait 高不一定代表 I/O 有性能瓶頸。當系統中只有 I/O 類型的進程在運行時,iowait 也會很高,但實際上,磁盤的讀寫遠沒有達到性能瓶頸的程度。

因此,碰到 iowait 升高時,需要先用 dstat、pidstat 等工具,確認是不是磁盤 I/O 的問題,然后再找是哪些進程導致了 I/O。

等待 I/O 的進程一般是不可中斷狀態,所以用 ps 命令找到的 D 狀態(即不可中斷狀態)的進程,多為可疑進程。但這個案例中,在 I/O 操作后,進程又變成了僵尸進程,所以不能用 strace 直接分析這個進程的系統調用。

這種情況下,我們用了 perf 工具,來分析系統的 CPU 時鐘事件,最終發現是直接 I/O 導致的問題。這時,再檢查源碼中對應位置的問題,就很輕松了。

而僵尸進程的問題相對容易排查,使用 pstree 找出父進程后,去查看父進程的代碼,檢查 wait() / waitpid() 的調用,或是 SIGCHLD 信號處理函數的注冊就行了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/86311.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/86311.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/86311.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ASP.NET Core + Jenkins 實現自動化發布

一、安裝Jenkins 我這邊服務器是Linux CentOS 7 &#xff0c;使用SSH 登錄云服務器后&#xff0c;輸入以下命令安裝jenkins. sudo wget -O /etc/yum.repos.d/jenkins.repo \https://pkg.jenkins.io/redhat-stable/jenkins.repo sudo rpm --import https://pkg.jenkins.io/red…

Java項目RestfulAPI設計最佳實踐

大家好&#xff0c;我是鋒哥。今天分享關于【Java項目RestfulAPI設計最佳實踐】面試題。希望對大家有幫助&#xff1b; Java項目RestfulAPI設計最佳實踐 超硬核AI學習資料&#xff0c;現在永久免費了&#xff01; 設計一個高效、易維護的 Java 項目中的 RESTful API 涉及到一…

FANUC機器人教程:用戶坐標系標定及其使用方法

目錄 概述 工作站創建 任務描述 用戶坐標系標定方法 用戶坐標系標定操作 用戶坐標系手動測試 用戶坐標系在程序中的應用 用戶坐標系選擇指令介紹 機器人示教編程 仿真運行 仿真案例資源下載 概述 FANUC機器人的用戶坐標系&#xff0c;是用戶對每個作業空間定義的直…

動態庫與靜態庫【Linux】

程序編譯過程 源代碼(.cpp) → 預處理(.i) → 編譯(.s) → 匯編(.o) → 鏈接(可執行文件) g -o main.i -E main.cpp 參數說明&#xff1a; 參數功能輸出文件類型-E僅預處理.i-S預處理 編譯.s-c預處理 編譯 匯編.o無完整流程&#xff08;預處理→編譯→匯編→鏈接&…

MySQL MHA 故障轉移-VIP

MHA故障轉移-VIP #手工在主庫添加VIP ifconfig ens33:1 192.168.80.200/24配置VIP腳本 vim /usr/local/bin/master_ip_failoverchmod x /usr/local/bin/#!/usr/bin/env perl use strict; use warnings FATAL > all;use Getopt::Long;my ( $command, $ssh_user, $orig_mast…

Elasticsearch索引字段的類型

在 Elasticsearch 中&#xff0c;索引字段的類型&#xff08;即 Mapping 中的字段類型&#xff09;對搜索和存儲性能影響很大。下面是各種常用數據類型的用途及推薦使用場景總結&#xff1a; 1. keyword 類型&#xff08;精確匹配&#xff09; 適合數據&#xff1a; 不需要分詞…

kubernetes證書續簽-使用kubeadm更新證書(下)

#作者&#xff1a;任少近 文章目錄 查看kubelet證書查看kubelet當前所使用的證書 更換 node上的kubelet證書生成node1所需要的kubelet.conf文件生成node2所需要的kubelet.conf文件查看csr 更新 ~/.kube/config 文件重啟相關組件 查看kubelet證書 以上少了kubelet的證書&#…

AI智能體長期記憶系統架構設計:從認知模型到生產實踐

1 長期記憶:AI智能體的認知基石 1.1 人類記憶與AI記憶的類比 #mermaid-svg-VIPKAFe7VgN4UHFA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VIPKAFe7VgN4UHFA .error-icon{fill:#552222;}#mermaid-svg-VIPKAFe7V…

快速上手:利用音頻大模型與Java提取視頻文案

文章目錄 1、前言2、需求說明2.1 需求說明2.2 數據準備 3、功能實現3.1 使用視頻理解大模型能力3.1.1 三方平臺視頻在線鏈接解析3.1.2 三方平臺視頻內網鏈接解析3.1.3 三方平臺視頻轉存本地服務 3.2 使用音頻識別大模型能力3.2.1 三方平臺視頻在線鏈接解析3.2.2 三方平臺視頻詳…

LLM復雜記憶存儲-多會話隔離案例實戰

導讀&#xff1a;在多用戶并發的對話系統中&#xff0c;會話隔離問題往往成為開發者面臨的技術難題。當數千個用戶同時與AI助手交互時&#xff0c;如何確保每個用戶的對話歷史完全獨立&#xff0c;避免數據混淆和隱私泄露&#xff1f; 本文深入剖析了基于RunnableWithMessageHi…

【PX4-AutoPilot教程-TIPS】PX4系統命令行控制臺ConsolesShells常用命令(持續更新)

PX4系統命令行控制臺 Consoles & Shells 常用命令 查看每個應用程序的堆棧使用情況獲取所有可用命令和APP的列表應用程序啟動、停止和狀態查詢查看本地文件系統查看剩余的可用RAM查看工作隊列中正在運行的內容以及運行速率查看特定的uORB話題調試uORB話題進行模式切換和故障…

國內優秀wordpress主題推薦

在國內&#xff0c;WordPress 主題市場雖然不如國外那樣龐大&#xff0c;但依然有許多優秀且適合中國用戶需求的主題。以下是一些經過評估和推薦的國內優秀WordPress主題&#xff0c;涵蓋不同類型的網站需求&#xff0c;如博客、企業官網、資源站、社區論壇等。 WP漢主題 WP漢…

第 6 章:進階話題

第 6 章&#xff1a;進階話題 過擬合vs欠擬合&#xff1a;模型復雜度和泛化能力的關系 在前面的章節中&#xff0c;我們已經學習了神經網絡的基礎知識、常見架構和基本訓練流程。然而&#xff0c;在實際的深度學習項目中&#xff0c;僅僅掌握這些基礎知識是不夠的。我們還需要…

4.2_1樸素模式匹配算法

知識總覽&#xff1a; 什么是字符串的模式匹配&#xff1a; 主串&#xff1a;想從該串獲取結果的串 模式串&#xff1a;想搜索的內容&#xff0c;不一定在主串中能搜到&#xff0c;子串一定能在主串中搜到 字符串模式匹配&#xff1a;在主串找模式串并返回找到的第一個模式串…

華為云Flexus+DeepSeek征文|華為云ModelArts搭建Dify-LLM應用開發平臺(AI智能選股大模型)

前言 在當今數字化時代&#xff0c;人工智能&#xff08;AI&#xff09;技術在金融領域的應用愈發廣泛&#xff0c;其中 AI 智能選股大模型備受關注。為了構建高效且精準的 AI 智能選股大模型&#xff0c;選擇合適的開發平臺和工具至關重要。華為云 ModelArts 作為一款面向 AI …

C4.5算法深度解析:決策樹進化的里程碑

C4.5是機器學習史上最經典的算法之一&#xff0c;由ID3之父Ross Quinlan在1993年提出。作為ID3的革命性升級&#xff0c;它不僅解決了前代的核心缺陷&#xff0c;更開創了連續特征處理和剪枝技術的先河&#xff0c;成為現代決策樹的奠基之作。 本文由「大千AI助手」原創發布&am…

leetcode 65

#include <string> #include <vector> #include <unordered_map> using namespace std;class Solution { public:bool isNumber(string s) {// 定義狀態轉移表vector<unordered_map<char, int>> states {{{ , 0}, {s, 1}, {d, 2}, {., 4}}, // …

微服務(nacos+myibatis)中如何在一個模塊調用多數據庫源的一種方案

#nacos配置默認數據庫 spring.datasource.typecom.alibaba.druid.pool.DruidDataSource spring.datasource.driverNamecom.mysql.jdbc.Driver #默認數據庫名 master spring.datasource.dynamic.primarymaster spring.datasource.dynamic.strictfalse spring.datasource.d…

高標準通信國際接軌,Ethercat與PROFINET網關實現全自動化生產線

在呼和浩特&#xff0c;集成商以其先進的食品飲料行業解決方案&#xff0c;為乳制品行業打造了一個智能化工廠的典范。這個工廠的核心是PROFINET全集成自動化&#xff08;TIA&#xff09;&#xff0c;它通過SIMATIC S7-1200 PLC和ethercat系統&#xff0c;構建了一個強大的PROF…

Netty 引用計數抽象類 AbstractReferenceCountedByteBuf 詳解

核心類圖 ----------------------------- ---------------------------------- | ReferenceCountUpdater | | AbstractReferenceCountedByteBuf | | <T extends ReferenceCounted>| | (extends AbstractByteBuf) | ----------…