調(diào)試學(xué)習(xí)1)
一個(gè)進(jìn)程可以簡(jiǎn)單想成Process │ ├── PID ├── 虛擬內(nèi)存空間 ├── 文件描述符 ├── socket ├── 環(huán)境變量 ├── 當(dāng)前工作目錄 ├── 信號(hào)狀態(tài) └── 一個(gè)或多個(gè)線程所以以后你排查一個(gè)機(jī)器人節(jié)點(diǎn)時(shí)其實(shí)是在看這個(gè)進(jìn)程 用了多少 CPU 用了多少內(nèi)存 開了多少 socket 開了多少文件 有多少線程 收到了什么 signal這就是 Linux 系統(tǒng)工程的核心視角。1、進(jìn)程之間怎么通信既然兩個(gè)進(jìn)程內(nèi)存默認(rèn)隔離那mocap_driver_node怎么把數(shù)據(jù)給gmr_retarget_node就需要IPC Inter-Process Communication 進(jìn)程間通信常見pipe socket shared memory message queue而 ROS2 Topic 本質(zhì)上也是在幫不同進(jìn)程之間進(jìn)行通信只不過它通過 DDS / RMW 做了大量封裝。所以進(jìn)程隔離 ↓ 不能直接隨便訪問對(duì)方內(nèi)存 ↓ 需要 IPC對(duì)比進(jìn)程線程內(nèi)存空間相對(duì)獨(dú)立同進(jìn)程共享創(chuàng)建開銷較大較小通信IPC直接共享內(nèi)存故障隔離較強(qiáng)較弱數(shù)據(jù)共享麻煩一些容易但要同步崩潰影響通常本進(jìn)程可能拖垮整個(gè)進(jìn)程舉個(gè)最直觀的例子Thread A 野指針崩了整個(gè)Process通常都會(huì)崩。不是只死 Thread A。例如你看到CPU 100%先問哪個(gè)進(jìn)程用top ps再問這個(gè)進(jìn)程哪個(gè)線程用top -H然后再哪個(gè)函數(shù)用perf于是完整鏈路系統(tǒng)異常 ↓ 進(jìn)程 ↓ 線程 ↓ 函數(shù)這就是前面 perf 那課已經(jīng)碰到的思路。查看自己的 shell PID在 Bashecho $$會(huì)輸出例如2450意思當(dāng)前 shell 進(jìn)程的 PID 2450這個(gè)$$以后寫 shell 腳本也會(huì)見到。查看 PID / PPID可以ps -ef看到UID PID PPID CMD skr 3000 2000 bash skr 3100 3000 ./robot_control說明robot_control PID 3100 父進(jìn)程 PID 3000也就是 bash。join()為什么重要假設(shè)std::thread worker(receiveLoop);main 要退出前worker.join();意思大概就是等 worker 真正結(jié)束。之前 TSan 課講過通知停止 ↓ 線程退出 ↓ join ↓ 釋放資源現(xiàn)在你應(yīng)該理解得更具體了。進(jìn)程狀態(tài)ps/top中可能看到R S D T Z現(xiàn)在先認(rèn)識(shí)最常見幾個(gè)。R Running / Runnable正在跑或者準(zhǔn)備被調(diào)度。S Sleeping等待事件比如socket數(shù)據(jù) condition variable timer非常正常。D Uninterruptible Sleep通常和某些 I/O 等待有關(guān)。Z Zombie 僵尸進(jìn)程后面會(huì)講。什么時(shí)候更適合進(jìn)程什么時(shí)候線程先簡(jiǎn)單記更適合多進(jìn)程如果你重視故障隔離 模塊邊界 獨(dú)立重啟 獨(dú)立部署例如mocap_driver_node gmr_node robot_output_node更適合多線程如果你重視低延遲共享數(shù)據(jù) 同一模塊內(nèi)并發(fā) 避免進(jìn)程間序列化例如同一個(gè) driver 內(nèi) receive thread processing thread現(xiàn)實(shí)系統(tǒng)經(jīng)常多進(jìn)程 每個(gè)進(jìn)程內(nèi)部多線程。而不是二選一。2、signal一句話signal Linux 內(nèi)核或其他進(jìn)程給某個(gè)進(jìn)程發(fā)送的一種異步通知。重點(diǎn)掌握SIGINT SIGTERM SIGKILL SIGSEGV SIGABRT SIGHUPSIGINTCtrlC你在終端運(yùn)行./robot_control然后按Ctrl C終端通常會(huì)給前臺(tái)進(jìn)程發(fā)送SIGINTINTInterrupt所以CtrlC → SIGINT它的意思大概是用戶請(qǐng)求中斷程序。終端發(fā) SIGINT ↓ ROS2 runtime 收到 ↓ 開始 shutdown ↓ executor 退出 ↓ node 析構(gòu) ↓ 進(jìn)程結(jié)束所以CtrlC其實(shí)就是 signal 機(jī)制在工作。SIGTERM請(qǐng)求進(jìn)程正常終止執(zhí)行kill 4321默認(rèn)發(fā)SIGTERMTERMTerminate可以理解請(qǐng)你盡快正常結(jié)束。進(jìn)程可以捕獲 SIGTERM然后停止接收新任務(wù) ↓ 停止控制循環(huán) ↓ 關(guān)閉 socket ↓ 斷開 EtherCAT ↓ 保存日志 ↓ 釋放資源 ↓ 退出所以對(duì)于機(jī)器人服務(wù)SIGTERM通常比 SIGKILL 更友好。SIGKILL真正的“強(qiáng)殺”執(zhí)行kill -9 4321這里9通常就是SIGKILLSIGKILL 非常特殊進(jìn)程不能捕獲、不能忽略、不能處理。Linux 直接終止它。所以kill -9確實(shí)很強(qiáng)程序沒有機(jī)會(huì)釋放資源 保存數(shù)據(jù) 關(guān)閉設(shè)備 safe disable 寫最后日志 join 線程SIGSEGV段錯(cuò)誤這個(gè)你前面已經(jīng)見過。SIGSEGV通常表示非法訪問內(nèi)存。例如int* p nullptr; *p 10;程序可能SIGSEGV ↓ Segmentation fault ↓ core dump然后你gdb core dump ASan排查。所以現(xiàn)在能串起來非法內(nèi)存訪問 ↓ 內(nèi)核發(fā)現(xiàn) ↓ 向進(jìn)程發(fā)送 SIGSEGV ↓ 默認(rèn)動(dòng)作終止 ↓ 可能生成 core dumpSIGABRTSIGABRTAbort一般意味著程序主動(dòng)或運(yùn)行庫要求“異常終止”。例如std::abort();會(huì)觸發(fā)SIGABRT另外某些嚴(yán)重運(yùn)行時(shí)錯(cuò)誤也可能最終導(dǎo)致SIGABRTSIGSEGV更多想到非法內(nèi)存訪問而SIGABRT更多想到程序/運(yùn)行庫主動(dòng)終止SIGHUPHangup歷史上和終端斷開有關(guān)。現(xiàn)代 Linux 服務(wù)里經(jīng)常把它用作重新加載配置例如某些 daemonkill -HUP PID不會(huì)完全退出而是重新讀取配置文件是不是這樣取決于具體程序。所以signal 的名字是一回事程序怎么處理又是另一回事。怎么查看 signal 編號(hào)可以kill -l會(huì)看到1 SIGHUP 2 SIGINT ... 9 SIGKILL ... 11 SIGSEGV 15 SIGTERM你不需要全部記。重點(diǎn)2 SIGINT 9 SIGKILL 11 SIGSEGV 15 SIGTERM以后看到exit signal 11你應(yīng)該反應(yīng)SIGSEGVkill -TERM有什么區(qū)別CtrlCSIGINTkill PIDSIGTERM它們都可以請(qǐng)求退出但來源和語義稍有不同。通常SIGINT 交互用戶中斷 SIGTERM 系統(tǒng)/管理工具請(qǐng)求終止例如systemd stop通常更接近SIGTERM所以服務(wù)程序一定應(yīng)該認(rèn)真處理 SIGTERM。怎么找到某個(gè)進(jìn)程 PID最基本ps -ef | grep robot_control也可以pgrep robot_control如果想看詳細(xì)pgrep -a robot_control例如4321 ./robot_control --config robot.yamlsignal 和 core dump 連起來有些 signal 默認(rèn)動(dòng)作會(huì)終止進(jìn)程 生成 core例如常見SIGSEGV SIGABRT所以程序崩了 ↓ 看日志里的 signal ↓ 判斷大方向 ↓ coredumpctl ↓ gdb這就是一條完整故障分析鏈。3、socketsocket 進(jìn)程和網(wǎng)絡(luò)協(xié)議棧之間的一個(gè)通信端點(diǎn)。例如你的程序 ↓ socket ↓ Linux 內(nèi)核網(wǎng)絡(luò)協(xié)議棧 ↓ 網(wǎng)卡 ↓ 網(wǎng)絡(luò)程序不會(huì)直接“控制網(wǎng)卡發(fā) UDP”。它通常是創(chuàng)建 socket ↓ bind / connect ↓ send / recv ↓ 內(nèi)核幫你處理協(xié)議和網(wǎng)卡socket() ↓ 內(nèi)核創(chuàng)建 socket 對(duì)象 ↓ 返回一個(gè) fd ↓ 程序以后通過 fd 操作它例如recv(fd, ...); send(fd, ...); close(fd);這就是 Linux 很經(jīng)典的Everything is a file-like interface不是說 socket 真的是磁盤文件而是 Linux 用統(tǒng)一的 fd 接口管理很多內(nèi)核資源。怎么查誰占用了端口以后非常實(shí)用。例如ss -lunp看 UDP?;蛘遱s -ltnp看 TCP。例如可能看到udp UNCONN 0 0 0.0.0.0:7014 ... users:((mocap,pid4321,fd7))這里你一下就看到port 7014 PID 4321 fd 7這三個(gè)概念全串起來了。socket buffer 是什么Linux 內(nèi)核會(huì)給 socket 維護(hù)發(fā)送緩沖區(qū) 接收緩沖區(qū)例如 UDP 包到了以后不是一定立刻進(jìn)你的用戶代碼。而是先進(jìn)入內(nèi)核 socket receive buffer然后你recvfrom()再把數(shù)據(jù)從內(nèi)核拿到用戶空間。如果你處理不夠快buffer 越積越多。最后滿可能開始丟包。EINTR一個(gè)阻塞系統(tǒng)調(diào)用被 signal 打斷了。例如recv() ↓ 正在阻塞 ↓ SIGINT到達(dá) ↓ recv返回錯(cuò)誤 ↓ errno EINTR所以signal和socket blocking I/O也會(huì)互相影響。這就是為什么 Linux 系統(tǒng)知識(shí)必須串著學(xué)。Network ↓ NIC ↓ Kernel Network Stack ↓ Socket Receive Buffer ↓ fd ↓ recvfrom() ↓ User Buffer ↓ Queue ↓ Worker這就是機(jī)器人系統(tǒng)工程師應(yīng)該逐漸形成的層次感。socket 內(nèi)核提供的通信對(duì)象socket() → 返回 fd以及blocking 沒數(shù)據(jù)時(shí)線程等待 non-blocking 沒數(shù)據(jù)立即返回4、file descriptorfd 進(jìn)程用來引用某個(gè)已打開內(nèi)核對(duì)象的整數(shù)編號(hào)。例如int fd open(config.txt, O_RDONLY);可能返回fd 3或者int sock socket(AF_INET, SOCK_DGRAM, 0);可能返回sock 4這里3 4都叫file descriptor簡(jiǎn)稱fd假設(shè)fd 5這個(gè)5本身不是文件也不是 socket。它只是當(dāng)前進(jìn)程里的一個(gè)“索引號(hào)”。可以想象成Process │ ├── fd 0 → stdin ├── fd 1 → stdout ├── fd 2 → stderr ├── fd 3 → config.txt ├── fd 4 → UDP socket └── fd 5 → device所以fd 進(jìn)程內(nèi)部訪問內(nèi)核資源的句柄open比如int fd open(/tmp/data.txt, O_RDONLY);大概可以理解應(yīng)用調(diào)用 open() ↓ 進(jìn)入內(nèi)核 ↓ 內(nèi)核找到文件 ↓ 建立打開狀態(tài) ↓ 給進(jìn)程分配一個(gè) fd ↓ 返回整數(shù)以后read(fd, ...)Linux 就知道這個(gè) fd 對(duì)應(yīng)哪個(gè)打開的對(duì)象。用完以后close(fd);意思我這個(gè)進(jìn)程不再需要這個(gè) fd 了。如果一直不 closefd 會(huì)一直占用這就叫fd leak 文件描述符泄漏怎么查看一個(gè)進(jìn)程打開了哪些 fdLinux 非常好用ls -l /proc/PID/fd例如ls -l /proc/4321/fd可能看到0 - /dev/pts/0 1 - /dev/pts/0 2 - /dev/pts/0 3 - socket:[123456] 4 - /tmp/robot.log 5 - socket:[123789]這非常直觀。如果你懷疑程序 fd 泄漏可以ls /proc/4321/fd | wc -l得到28過 10 分鐘120再過 10 分鐘300那就非??梢?。如果數(shù)目一直增長(zhǎng)不下降說明可能有 fd 泄漏。另一個(gè)非常常用的工具lsof -p 4321lsoflist open files它可以列出進(jìn)程打開的普通文件 socket pipe 設(shè)備 共享庫非常適合查這個(gè)進(jìn)程到底打開了什么fd 是進(jìn)程內(nèi)部編號(hào)。所以fd 3單獨(dú)拿出來沒有意義。必須結(jié)合哪個(gè)進(jìn)程一起看。PID fd 才能真正定位資源所以你以后經(jīng)??吹絇ID 4321 fd 7意思是進(jìn)程 4321 的第 7 號(hào)文件描述符。這就是為什么ss -lunp可能顯示pid4321,fd7現(xiàn)在你應(yīng)該能完全看懂了。同一個(gè)進(jìn)程的線程共享 fd 表。比如Thread A 創(chuàng)建 socket fd7Thread B 通常也可以使用 fd7因?yàn)閒d table屬于進(jìn)程資源。這也意味著Thread A close(fd)而 Thread B 正在recv(fd)可能產(chǎn)生復(fù)雜的并發(fā)行為。所以 fd 生命周期也要同步。shutdown()和close()TCP socket 經(jīng)常見shutdown(fd, SHUT_RDWR); close(fd);簡(jiǎn)單理解shutdown 關(guān)閉 socket 通信方向例如不再讀 不再寫而close 當(dāng)前進(jìn)程釋放這個(gè) fd兩者不是同一個(gè)概念。現(xiàn)階段知道區(qū)別即可不需要深入 TCP 狀態(tài)機(jī)。5、pollpoll 把一組 fd 交給內(nèi)核問“這里面哪些現(xiàn)在可以讀、可以寫、或者出錯(cuò)了”例如fd 3 → UDP A fd 4 → UDP B fd 5 → TCP fd 6 → pipe你不需要輪流 recv 輪流試 輪流空轉(zhuǎn)而是poll() ↓ 線程睡眠 ↓ 某個(gè) fd 有事件 ↓ 內(nèi)核喚醒線程 ↓ 你處理對(duì)應(yīng) fd沒有任何 fd 有事 ↓ 線程睡眠只有數(shù)據(jù)來了 連接斷了 超時(shí)了才醒。所以poll/epoll本質(zhì)是在做“事件驅(qū)動(dòng)等待”。POLLIN這個(gè) fd 現(xiàn)在有數(shù)據(jù)可以讀。比如UDP 包到了 ↓ POLLIN或者 TCP對(duì)端數(shù)據(jù)到了 ↓ POLLIN程序看到if (fds[i].revents POLLIN) { recv(...); }就去讀。poll timeout例如poll(fds, n, 1000);情況 1某個(gè) fd 很快有事件比如 20 ms 后來了數(shù)據(jù)20ms ↓ poll 立即返回不會(huì)傻等 1 秒。情況 21 秒都沒事件poll 返回 0表示timeout。情況 3出錯(cuò)poll 返回負(fù)數(shù)再看errnoepollepoll 是 Linux 專門為大量 fd 事件管理設(shè)計(jì)的一套機(jī)制。你可以先理解poll 每次把整組 fd 拿去問內(nèi)核 epoll 先把要關(guān)心的 fd 注冊(cè)給內(nèi)核 以后只等“哪些 fd 真有事件”這是最重要的區(qū)別之一。epoll 常見三步① epoll_create ↓ 創(chuàng)建 epoll 實(shí)例 ② epoll_ctl ↓ 把 fd 注冊(cè)進(jìn)去 ③ epoll_wait ↓ 等待事件也就是說創(chuàng)建一個(gè)“事件管理器” ↓ 告訴它我關(guān)心哪些 fd ↓ 以后一直問 誰有事fd 數(shù)量少 邏輯簡(jiǎn)單 → poll 很合適 大量 fd 高并發(fā)連接 → epoll 更適合最常見EPOLLIN表示可讀還有EPOLLOUT表示可寫以及EPOLLERR表示錯(cuò)誤EPOLLHUP表示掛斷 / 對(duì)端關(guān)閉相關(guān)事件現(xiàn)在先認(rèn)識(shí)即可。LT 和 ETepoll 里以后很容易聽到Level Triggered LT和Edge Triggered ET先用大白話理解。LT只要還有數(shù)據(jù)就一直提醒你假設(shè) socket 里有 1000 bytes你只讀100 bytes還剩900 bytesLT 模式下下次 epoll_wait ↓ 還會(huì)告訴你 這個(gè) fd 可讀因?yàn)槔锩孢€有數(shù)據(jù)這比較容易寫對(duì)。ET狀態(tài)變化時(shí)重點(diǎn)提醒一次ET 更像剛才沒數(shù)據(jù) ↓ 現(xiàn)在有數(shù)據(jù) ↓ 提醒你如果你收到事件以后只讀了一點(diǎn)剩下的數(shù)據(jù)可能不會(huì)像 LT 那樣持續(xù)提醒你。所以 ET 常要求non-blocking 一直讀到 EAGAIN也就是一次事件來了 ↓ 把當(dāng)前能讀的數(shù)據(jù)盡量讀干凈LT 只要條件還成立就繼續(xù)提醒ET 更強(qiáng)調(diào)狀態(tài)邊沿變化需要更謹(jǐn)慎處理實(shí)際項(xiàng)目里先把 LT 寫對(duì)比為了“高級(jí)”強(qiáng)上 ET 更重要。poll / epoll 和 blocking/non-blockingpoll/epoll non-blocking socket為什么因?yàn)橄扔?poll/epoll 等待 ↓ 確認(rèn) fd 有事件 ↓ 再 read/recv如果某些邊界情況下狀態(tài)已經(jīng)變了non-blocking 可以避免線程意外卡死在 read尤其 ET 模式里更常見。eventfd先認(rèn)識(shí)概念。Linux 可以創(chuàng)建一種eventfd它本身也是fd可以用來線程間通知事件例如main thread ↓ 要停止 receiver ↓ 向 eventfd 寫一下而 receiver 線程epoll_wait()同時(shí)等socket fd eventfd于是數(shù)據(jù)來了 → 處理數(shù)據(jù) 停止事件來了 → 退出線程這個(gè)設(shè)計(jì)非常漂亮。timerfdLinux 還有timerfd它也是 fd。你可以創(chuàng)建一個(gè)定時(shí)器 ↓ 定時(shí)到期 ↓ 這個(gè) fd 變成可讀于是 epoll 可以同時(shí)等socket timer event全部統(tǒng)一成 fd。這就是 Linux fd 抽象很強(qiáng)的地方。只解決怎么高效等多個(gè) I/O 事件。如果收到一幀 ↓ 處理 50ms那你的 event loop 還是被拖住。常見架構(gòu)epoll thread ↓ 快速收數(shù)據(jù) ↓ 投遞 queue ↓ worker thread處理所以I/O multiplexing和task parallelism是兩件事。I/O multiplexing這就是 poll/epoll 所屬的大類I/O multiplexing I/O 多路復(fù)用意思不是同時(shí)傳很多路數(shù)據(jù)而是一個(gè)執(zhí)行線程同時(shí)管理多個(gè) I/O fd 的就緒事件。這個(gè)詞面試很常見。select 老牌方案有 fd 數(shù)量/接口方面限制poll 接口更直接沒有 select 那種固定 fd_set 限制epoll Linux 特有更適合大量 fd 高并發(fā)事件管理不要現(xiàn)在深挖每個(gè)內(nèi)核復(fù)雜度。怎么判斷 poll/epoll 問題比如進(jìn)程CPU 100%strace 發(fā)現(xiàn)一直recvfrom(...) -1 EAGAIN recvfrom(...) -1 EAGAIN recvfrom(...) -1 EAGAIN瘋狂刷。這通常說明non-blocking socket busy polling可能需要poll/epoll而不是不停重試。6、CPU以后top里可能看到us sy id wa先認(rèn)識(shí)最重要幾個(gè)。us user用戶態(tài)代碼占用。比如GMR Eigen protobuf 你的 C 邏輯sy system內(nèi)核態(tài) CPU。比如socket 系統(tǒng)調(diào)用 文件 I/O 網(wǎng)絡(luò)棧 驅(qū)動(dòng)id idleCPU 空閑。wa I/O waitCPU 在等待某些 I/O 完成相關(guān)狀態(tài)。現(xiàn)階段先有這個(gè)概念即可。上下文切換是什么假設(shè) CPU 只有一個(gè)核。現(xiàn)在Thread A Thread B Thread C都想運(yùn)行。CPU 不可能真正同時(shí)跑三個(gè)。于是跑 A ↓ 保存 A 狀態(tài) ↓ 跑 B ↓ 保存 B 狀態(tài) ↓ 跑 C這就叫context switch 上下文切換多線程不是免費(fèi)的。線程太多可能帶來調(diào)度開銷 cache miss 上下文切換所以線程不是越多越快。memory程序內(nèi)存一個(gè)進(jìn)程的內(nèi)存可以粗略想成Process Memory │ ├── code ├── stack ├── heap ├── shared libraries ├── mmap └── shared memory你現(xiàn)在不用背內(nèi)存布局。重點(diǎn)是理解幾個(gè)常見指標(biāo)VIRT RES / RSS SHRVIRTVirtual Memory簡(jiǎn)單理解進(jìn)程擁有或映射的虛擬地址空間總量。例如VIRT 8 GB不代表真的用了 8 GB 物理內(nèi)存因?yàn)槔锩婵赡馨ü蚕韼?mmap 預(yù)留地址空間 尚未實(shí)際觸碰的內(nèi)存所以不要看到 VIRT 很大就直接判斷內(nèi)存泄漏。RSSResident Set Sizetop里經(jīng)常叫RES它更接近當(dāng)前真正駐留在物理 RAM 里的進(jìn)程內(nèi)存。所以如果你排查程序是不是實(shí)際越來越吃內(nèi)存通常RSS / RES比VIRT更有參考價(jià)值。SHRShared Memory簡(jiǎn)單理解其中有一部分內(nèi)存是可共享的。例如動(dòng)態(tài)庫代碼頁 共享內(nèi)存多個(gè)進(jìn)程可能共同使用。所以RES也不能簡(jiǎn)單相加就認(rèn)為等于整機(jī)實(shí)際獨(dú)占內(nèi)存。內(nèi)存統(tǒng)計(jì)比 CPU 稍微復(fù)雜。OOMOut Of Memory內(nèi)存耗盡。Linux 在極端情況下可能啟動(dòng)OOM Killer選擇某些進(jìn)程殺掉以保證系統(tǒng)繼續(xù)運(yùn)行。日志里可能看到Killed process xxxx Out of memory機(jī)器人上如果控制程序被 OOM Killer 干掉控制進(jìn)程突然消失所以生產(chǎn)系統(tǒng)要關(guān)注內(nèi)存邊界。swap如果 RAM 不夠Linux 可能把部分內(nèi)存頁交換到swap可以簡(jiǎn)單理解RAM 不夠 ↓ 把一部分暫時(shí)不用的頁面放到磁盤普通桌面系統(tǒng)這是正常機(jī)制。但實(shí)時(shí)控制非常不喜歡page 被 swap out ↓ 實(shí)時(shí)線程突然訪問 ↓ 需要從磁盤調(diào)回來 ↓ 延遲巨大這就是后面為什么要學(xué)mlockallCPU cacheCPU 比 RAM 快很多。所以現(xiàn)代 CPU 有L1 L2 L3 cache如果數(shù)據(jù)剛好在 cache訪問快如果總是cache miss就要去更慢的內(nèi)存層級(jí)。所以性能問題不只是“這個(gè)函數(shù)做了多少次加法”還包括數(shù)據(jù)布局 內(nèi)存訪問模式 cache locality這就是為什么連續(xù)數(shù)組通常很高效。怎么查看進(jìn)程內(nèi)存很簡(jiǎn)單。例如ps -o pid,%cpu,%mem,rss,vsz,cmd -p 4321可能看到PID %CPU %MEM RSS VSZ 4321 80.0 2.5 ... ...其中RSS通常單位是 KB。CPU / memory 排障可以形成這個(gè)流程程序變慢① 看 CPU ↓ 哪個(gè)進(jìn)程高 ② 看線程 ↓ 哪個(gè)線程高 ③ perf ↓ 哪個(gè)函數(shù)高如果CPU 不高 但機(jī)器很卡再看memory swap I/O wait page fault如果內(nèi)存持續(xù)增長(zhǎng)再RSS趨勢(shì) ↓ ASan/LSan/Valgrind ↓ 代碼生命周期CPU 100% ≈ 一個(gè)邏輯核滿載CPU 100% ≈ 多線程用了多個(gè)核VIRT 虛擬地址空間RSS / RES 更接近實(shí)際駐留物理內(nèi)存