最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux進(jìn)程內(nèi)存監(jiān)測(cè)與內(nèi)存泄漏的檢測(cè)方法

 更新時(shí)間:2026年06月16日 08:45:50   作者:碼農(nóng)愛(ài)學(xué)習(xí)  
在嵌入式Linux開(kāi)發(fā)中,了解內(nèi)存泄漏至關(guān)重要,本文介紹了如何通過(guò)查看status文件和smaps文件中的VmRSS和USS來(lái)監(jiān)測(cè)Linux進(jìn)程內(nèi)存使用情況,并通過(guò)cJSON解析示例展示了內(nèi)存泄漏的成因及正確處理方法,需要的朋友可以參考下

在嵌入式Linux開(kāi)發(fā)中,如果存在編程不當(dāng),申請(qǐng)的內(nèi)存未按預(yù)期釋放,就會(huì)存在內(nèi)存泄漏的情況,嚴(yán)重的時(shí)候會(huì)導(dǎo)致整個(gè)程序因oom而崩潰,本篇先簡(jiǎn)單介紹一些Linux系統(tǒng)中查看指定進(jìn)程的內(nèi)存使用情況的方法,并通過(guò)一個(gè)實(shí)例來(lái)對(duì)比查看出現(xiàn)內(nèi)存泄漏后的內(nèi)存占用情況。

1 Linux進(jìn)程內(nèi)存使用情況的查看方法

這里暫且先介紹兩種方式,查看status文件和查看smaps文件

1.1 proc/pid/status中的VmRSS

在 Linux 系統(tǒng)中,/proc/[pid]/status 文件中的 ‌VmRSS‌(Virtual Memory Resident Set Size)表示進(jìn)程當(dāng)前實(shí)際占用的‌物理內(nèi)存大小‌。

示例:

1.2 proc/pid/smaps中的USS(Private_Clean + Private_Dirty)

在 Linux 系統(tǒng)的 /proc/[pid]/smaps文件中,‌Private_Clean‌ 和 ‌Private_Dirty‌ 之和代表了該進(jìn)程‌獨(dú)占的、實(shí)際駐留在物理內(nèi)存中的頁(yè)面大小‌。

  • ‌**Private (私有)**‌: 指該內(nèi)存頁(yè)僅被當(dāng)前進(jìn)程引用(引用計(jì)數(shù)為 1),其他進(jìn)程無(wú)法訪問(wèn)。這通常包括進(jìn)程的堆(heap)、棧(stack)以及私有數(shù)據(jù)段。
  • Clean (干凈)‌: 指該內(nèi)存頁(yè)的內(nèi)容與 backing store(如磁盤上的文件映射或初始狀態(tài))‌一致‌,未被修改過(guò)。如果系統(tǒng)需要回收內(nèi)存,可以直接丟棄這些頁(yè),無(wú)需寫回磁盤。
  • Dirty (臟)‌: 指該內(nèi)存頁(yè)的內(nèi)容‌已被修改‌,與原始來(lái)源不一致。如果系統(tǒng)需要回收這些頁(yè),必須先將數(shù)據(jù)寫回交換分區(qū)(Swap)或關(guān)聯(lián)的文件中。

Private_CleanPrivate_Dirty 之和即為該進(jìn)程的 ‌USS‌(Unique Set Size,唯一集大?。?/p>

示例:

一共18個(gè)內(nèi)存分段:

  • 程序自身 ELF 文件映射段(3 段)
    • 55ee5c381000-55ee5c389000 r-xp … test5:可執(zhí)行代碼段 (.text)
    • 55ee5c588000-55ee5c589000 r–p … test5:只讀常量段 (.rodata)
    • 55ee5c589000-55ee5c58a000 rw-p … test5:全局?jǐn)?shù)據(jù)段 (.data/.bss)
  • 進(jìn)程堆段(1 段)
    • 55ee5d904000-55ee5d925000 rw-p … [heap]:堆內(nèi)存
  • libc-2.27.so C 標(biāo)準(zhǔn)庫(kù)映射(5 段)
    • 7f8fb5397000-7f8fb557e000 r-xp … libc-2.27.so:代碼段.text
    • 7f8fb557e000-7f8fb577e000 —p … libc-2.27.so:保護(hù)隔離頁(yè) (Gap 防護(hù)頁(yè))
    • 7f8fb577e000-7f8fb5782000 r–p … libc-2.27.so:只讀常量段.rodata
    • 7f8fb5782000-7f8fb5784000 rw-p … libc-2.27.so:全局?jǐn)?shù)據(jù)段
    • 7f8fb5784000-7f8fb5788000 rw-p …(匿名):匿名私有段
  • 動(dòng)態(tài)鏈接器 ld-2.27.so 映射(5 段)
    • 7f8fb5788000-7f8fb57b1000 r-xp … ld-2.27.so:鏈接器代碼段
    • 7f8fb598b000-7f8fb598d000 rw-p …(匿名):鏈接器運(yùn)行時(shí)匿名內(nèi)存
    • 7f8fb59b1000-7f8fb59b2000 r–p … ld-2.27.so:鏈接器只讀常量段
    • 7f8fb59b2000-7f8fb59b3000 rw-p … ld-2.27.so:鏈接器全局?jǐn)?shù)據(jù)段
    • 7f8fb59b3000-7f8fb59b4000 rw-p …(匿名):鏈接器額外匿名私有內(nèi)存
  • 內(nèi)核預(yù)留特殊匿名段(4 段)
    • 7ffe646ef000-7ffe64710000 rw-p … [stack]:主線程棧
    • 7ffe64798000-7ffe6479b000 r–p … [vvar]:內(nèi)核只讀變量頁(yè)
    • 7ffe6479b000-7ffe6479c000 r-xp … [vdso]:虛擬動(dòng)態(tài)共享庫(kù)
    • ffffffff600000-ffffffffff601000 --xp … [vsyscall]:老式系統(tǒng)調(diào)用兼容段

1.3 獲取RSS與USS的簡(jiǎn)易腳本

USS的計(jì)算:

  • 首先通過(guò)ps和grep、awk指令過(guò)濾出指定進(jìn)程(本例中是名為test5的程序)的進(jìn)程id
  • 然后讀取該進(jìn)程id的smaps文件,再通過(guò)awk指令計(jì)算出Private_Clean + Private_Dirty得到USS

RSS的計(jì)算:

  • 讀取該進(jìn)程id的status文件,grep指令找到VmRSS對(duì)應(yīng)的大小
#!/bin/bash

PID=`ps -aux | grep test5 | grep -v grep | grep -v test5.c | awk '{print $2}'`
FILE=/proc/$PID/smaps

if [ ! -f "$FILE" ]; then
    echo "進(jìn)程 $PID 不存在"
    exit 1
fi

# 計(jì)算 USS = Private_Clean + Private_Dirty
USS_KB=$(awk '/Private_Clean/ {pc += $2}
               /Private_Dirty/ {pd += $2}
               END {print pc + pd}' "$FILE")

RSS_KB=$(grep VmRSS /proc/$PID/status | awk '{print $2}')

echo "PID: $PID"
echo "USS: $USS_KB KB ($((USS_KB/1024)) MB)"
echo "RSS: $RSS_KB KB ($((RSS_KB/1024)) MB)"

這里解釋下USS_KB的計(jì)算:

USS_KB=$(awk '/Private_Clean/ {pc += $2}
  • USS_KB=:定義 Shell 變量,用來(lái)存儲(chǔ)最終計(jì)算結(jié)果
  • $(...):命令替換,把括號(hào)內(nèi)命令的輸出賦值給變量
  • awk:調(diào)用 awk 文本處理工具逐行掃描 $FILE 文件
  • /Private_Clean/:awk 匹配規(guī)則:只要當(dāng)前行包含字符串 Private_Clean
  • {pc += $2}:匹配成功后執(zhí)行的動(dòng)作
    • $2:當(dāng)前行的第二個(gè)字段(默認(rèn)以空格 / 制表符分割列)
    • pc:自定義變量,初始值默認(rèn)為 0,pc += $2,即所有Private_Clean行的第 2 列數(shù)值全部累加存入pc
/Private_Dirty/ {pd += $2}
  • 類似的,所有Private_Dirty行的第 2 列數(shù)值全部累加存入pd
END {print pc + pd}' "$FILE")
  • END { ... }:awk 內(nèi)置結(jié)束塊,等文件所有行全部遍歷處理完之后,只執(zhí)行一次
  • print pc + pd:打印兩個(gè)累加值相加的結(jié)果
  • "$FILE":awk 腳本結(jié)束,指定要處理的文件是 Shell 變量 $FILE

該腳本運(yùn)行一次檢查一次,若想持續(xù)運(yùn)行,也可繼續(xù)修改腳本,使之按指定時(shí)間間隔執(zhí)行。

2 內(nèi)存泄漏示例舉例

這里以cJSON解析示例,cJSON_Parse會(huì)申請(qǐng)內(nèi)存,生成json樹,在使用完會(huì),需要通過(guò)cJSON_Delete來(lái)釋放內(nèi)存。

2.1 存在內(nèi)存泄漏的寫法

這里test_parse_json是對(duì)json數(shù)據(jù)(從json文件讀取,先保存在buf緩存中)進(jìn)行解析和處理,期望解析得到的json樹在外部釋放。

這里的問(wèn)題是,cJSON *jRoot不應(yīng)該使用一次指針,而應(yīng)該使用二級(jí)指針。

簡(jiǎn)單分析:

  • 參數(shù)cJSON *jRoot雖然是指針,但按照參數(shù)按值傳遞的原則,是傳入的外部cJSON *jRoot的NULL值(外部cJSON *jRoot是實(shí)參,其值NULL傳給函數(shù)參數(shù)中的形參cJSON *jRoot
  • 內(nèi)部的jRoot,實(shí)際是另一個(gè)副本,初始值就是傳入的NULL,然后接受cJSON_Parse的賦值
  • test_parse_json退出后,外部的cJSON *jRoot實(shí)際未被修改,仍為NULL,所以未能釋放內(nèi)存,造成內(nèi)存泄漏
// 錯(cuò)誤示例
void test_parse_json(char *buf, cJSON *jRoot)
{
    jRoot = cJSON_Parse(buf);
    if (jRoot)
    {
        cJSON *json = NULL;
        
        if ((json = cJSON_GetObjectItem(jRoot, "version")))
        {
            printf("[%s:%d] version:%d\n", __func__, __LINE__, json->valueint);
        }
        
        char *data = cJSON_PrintUnformatted(jRoot);
        printf("[%s:%d] ok:%s\n", __func__, __LINE__, data);
        free(data);
    }
}

//================== 使用示例
cJSON *jRoot = NULL;
test_parse_json(buf, jRoot);

if (jRoot)
{
    cJSON_Delete(jRoot); // 釋放cJSON_Parse申請(qǐng)的內(nèi)存
    printf("[%s:%d] [%d] cJSON_Delete\n", __func__, __LINE__, i);
}
else
{
    printf("[%s:%d] [%d] err, null jRoot!\n", __func__, __LINE__, i);
}

如果在cJSON_Delete時(shí),沒(méi)有對(duì)jRoot是NULL的錯(cuò)誤打印,問(wèn)題將被掩蓋,因?yàn)閖son解析代碼運(yùn)行結(jié)果是正常的。

2.2 正確的寫法

正確的做法是使用二級(jí)指針。

簡(jiǎn)單分析:

  • 參數(shù)cJSON **jRoot是二級(jí)指針,但按照參數(shù)按值傳遞的原則,是傳入的外部cJSON *jRoot的地址(外部cJSON *jRoot是實(shí)參,其地址值傳給函數(shù)參數(shù)中的形參cJSON **jRoot
  • 內(nèi)部的jRoot,實(shí)際是另一個(gè)副本,初始值就是傳入的外部cJSON *jRoot的地址,而*jRoot就是外部cJSON *jRoot所存儲(chǔ)的值,然后接受cJSON_Parse的賦值,這里,外部的cJSON *jRoot就記錄到了內(nèi)部cJSON_Parse申請(qǐng)的內(nèi)存地址
  • test_parse_json退出后,外部的cJSON *jRoot就可以釋放內(nèi)存

關(guān)于二級(jí)指針的介紹,具體可看之前的文章

// 正確示例
void test_parse_json_2(char *buf, cJSON **jRoot)
{
    *jRoot = cJSON_Parse(buf);
    if (*jRoot)
    {
        cJSON *json = NULL;

        if ((json = cJSON_GetObjectItem(*jRoot, "version")))
        {
            printf("[%s:%d] version:%d\n", __func__, __LINE__, json->valueint);
        }
        
        char *data = cJSON_PrintUnformatted(*jRoot);
        printf("[%s:%d] ok:%s\n", __func__, __LINE__, data);
        free(data);
    }
}

//================== 使用示例
cJSON *jRoot = NULL;
test_parse_json_2(buf, &jRoot);

if (jRoot)
{
    cJSON_Delete(jRoot); // 釋放cJSON_Parse申請(qǐng)的內(nèi)存
    printf("[%s:%d] [%d] cJSON_Delete\n", __func__, __LINE__, i);
}
else
{
    printf("[%s:%d] [%d] err, null jRoot!\n", __func__, __LINE__, i);
}

2.3 完整示例代碼

// gcc test5.c cjson/cJSON.c -o test5
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "cjson/cJSON.h"
#define CONFIG_FILE1 "config1.json"
int read_json_file(const char *filePath, char **buf, int *buf_len)
{   
    FILE *fp = fopen(filePath, "r");
    if (!fp) 
    {
        printf("fopen:%s failed\n", filePath);
        return -1;
    }
    // 計(jì)算文件的大小
    fseek(fp, 0, SEEK_END);
    *buf_len = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    // 分配內(nèi)存
    *buf = malloc(*buf_len + 1);
    if (NULL == *buf) 
    {
        fclose(fp);
        return -1;
    }
    memset(*buf, 0, *buf_len + 1);  // 清空,防止臟數(shù)據(jù)
    // 讀取內(nèi)存
    fread(*buf, 1, *buf_len, fp);
    fclose(fp);
    cJSON_Minify(*buf); // 刪除注釋+壓縮
    printf("[%s:%d] buf_len:%d, buf:%s]\n", __func__, __LINE__, *buf_len, *buf);
    return 0;
}
// 錯(cuò)誤示例
void test_parse_json(char *buf, cJSON *jRoot)
{
    jRoot = cJSON_Parse(buf);
    if (jRoot)
    {
        cJSON *json = NULL;
        if ((json = cJSON_GetObjectItem(jRoot, "version")))
        {
            printf("[%s:%d] version:%d\n", __func__, __LINE__, json->valueint);
        }
        char *data = cJSON_PrintUnformatted(jRoot);
        printf("[%s:%d] ok:%s\n", __func__, __LINE__, data);
        free(data);
    }
}
// 正確示例
void test_parse_json_2(char *buf, cJSON **jRoot)
{
    *jRoot = cJSON_Parse(buf);
    if (*jRoot)
    {
        cJSON *json = NULL;
        if ((json = cJSON_GetObjectItem(*jRoot, "version")))
        {
            printf("[%s:%d] version:%d\n", __func__, __LINE__, json->valueint);
        }
        char *data = cJSON_PrintUnformatted(*jRoot);
        printf("[%s:%d] ok:%s\n", __func__, __LINE__, data);
        free(data);
    }
}
int main(void)
{   
    char *buf = NULL;
    int buf_len = 0;
    read_json_file(CONFIG_FILE1, &buf, &buf_len);
    printf("[%s:%d] buf_len:%d, buf:%s]\n", __func__, __LINE__, buf_len, buf);
    for (int i = 0; i < 5000; i++) // 多循環(huán)幾次,效果更明顯
    {
        cJSON *jRoot = NULL;
#if 0
        // 錯(cuò)誤示例
        test_parse_json(buf, jRoot);
#else   
        // 正確示例
        test_parse_json_2(buf, &jRoot);
#endif
        if (jRoot)
        {
            cJSON_Delete(jRoot); // 釋放cJSON_Parse申請(qǐng)的內(nèi)存
            printf("[%s:%d] [%d] cJSON_Delete\n", __func__, __LINE__, i);
        }
        else
        {
            printf("[%s:%d] [%d] err, null jRoot!\n", __func__, __LINE__, i);
        }
    }
    // 釋放內(nèi)存
    free(buf);
    buf = NULL;
    while(1)
    {
        sleep(1); // 程序先暫停在這里,便于觀察內(nèi)存使用情況
    }
    return 0;
}

3 運(yùn)行結(jié)果

運(yùn)行沒(méi)有內(nèi)存泄漏的分支,可以看到在循環(huán)5000次后,無(wú)論是USS還是RSS,都只有幾百K

運(yùn)行有內(nèi)存泄漏的分支,可以看到在循環(huán)5000次后,因?yàn)槊看味紱](méi)有釋放json,最終USS是約5MB,RSS是約6MB,對(duì)比可見(jiàn)內(nèi)存未釋放

4 總結(jié)

本篇初步介紹了Linux進(jìn)程內(nèi)存監(jiān)測(cè)的簡(jiǎn)單方案,包括查看status和smaps文件中相關(guān)內(nèi)存數(shù)據(jù)記錄,然后通過(guò)cJSON的實(shí)例,對(duì)比有無(wú)內(nèi)存泄漏的情況下,通過(guò)查詢?cè)撨M(jìn)程的USS和RSS的大小,確認(rèn)內(nèi)存泄漏的存在。

以上就是Linux進(jìn)程內(nèi)存監(jiān)測(cè)與內(nèi)存泄漏的檢測(cè)方法的詳細(xì)內(nèi)容,更多關(guān)于Linux內(nèi)存監(jiān)測(cè)與內(nèi)存泄漏檢測(cè)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Linux 每天自動(dòng)備份mysql數(shù)據(jù)庫(kù)的方法

    Linux 每天自動(dòng)備份mysql數(shù)據(jù)庫(kù)的方法

    linux下為了安全有時(shí)候需要自動(dòng)備份mysql數(shù)據(jù)庫(kù),下面是具體的實(shí)現(xiàn)步驟。感興趣的朋友跟隨小編一起看看吧
    2009-09-09
  • 101個(gè)腳本之建立linux回收站的腳本

    101個(gè)腳本之建立linux回收站的腳本

    眾所周知,linux是沒(méi)有回收站的,一些人很害怕刪錯(cuò)東西(有經(jīng)驗(yàn)的linux管理員極少范這錯(cuò)誤),個(gè)人不建議回收站,而應(yīng)該是培養(yǎng)個(gè)人的安全意識(shí)。有點(diǎn)小跑題
    2016-08-08
  • Linux 下目錄文件權(quán)限(命令)的查看和修改

    Linux 下目錄文件權(quán)限(命令)的查看和修改

    這篇文章主要介紹了Linux 下目錄文件權(quán)限(命令)的查看和修改的相關(guān)資料,需要的朋友可以參考下
    2016-11-11
  • Linux服務(wù)器安裝GRUB步驟

    Linux服務(wù)器安裝GRUB步驟

    在本篇文章中我們給大家整理了Linux服務(wù)器安裝GRUB的詳細(xì)步驟以及相關(guān)注意事項(xiàng),有需要的朋友們參考下。
    2018-09-09
  • Linux鏡像拉取失敗的解決方案

    Linux鏡像拉取失敗的解決方案

    文章總結(jié):本文介紹了兩個(gè)Docker鏡像拉取錯(cuò)誤的原因及解決方法,第一個(gè)問(wèn)題是由于鏡像源速度太慢導(dǎo)致超時(shí),解決方法是更換更好的鏡像源,第二個(gè)問(wèn)題是連接被拒絕,解決方法同樣是更換鏡像源,希望本文能為大家提供幫助,也歡迎在腳本之家分享更多經(jīng)驗(yàn)
    2026-03-03
  • LINUX磁盤分區(qū)、格式化、掛載、卸載詳細(xì)過(guò)程

    LINUX磁盤分區(qū)、格式化、掛載、卸載詳細(xì)過(guò)程

    這篇文章主要介紹了LINUX磁盤分區(qū)、格式化、掛載、卸載詳細(xì)過(guò)程,具有一定的參考價(jià)值,有需要的可以了解一下。
    2016-11-11
  • Linux文件系統(tǒng)之inode與軟硬鏈接詳解

    Linux文件系統(tǒng)之inode與軟硬鏈接詳解

    這篇文章討論的話題是沒(méi)有被打開(kāi)的文件,文件等于文件內(nèi)容加文件屬性,沒(méi)打開(kāi)的文件一定是存儲(chǔ)在磁盤上的,文件內(nèi)容以數(shù)據(jù)塊的形式進(jìn)行存儲(chǔ),文件屬性以 inode 的形式進(jìn)行存儲(chǔ),文中通過(guò)圖文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03
  • 詳解Linux下掛載新硬盤方法

    詳解Linux下掛載新硬盤方法

    這篇文章主要介紹了詳解Linux下掛載新硬盤方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-05-05
  • Linux下實(shí)現(xiàn)修改時(shí)區(qū)為東八區(qū)

    Linux下實(shí)現(xiàn)修改時(shí)區(qū)為東八區(qū)

    這篇文章主要介紹了Linux下實(shí)現(xiàn)修改時(shí)區(qū)為東八區(qū),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2026-06-06
  • Linux進(jìn)程間通信之管道如何實(shí)現(xiàn)進(jìn)程池

    Linux進(jìn)程間通信之管道如何實(shí)現(xiàn)進(jìn)程池

    這篇文章主要介紹了Linux進(jìn)程間通信之管道如何實(shí)現(xiàn)進(jìn)程池問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-03-03

最新評(píng)論

民和| 连州市| 巧家县| 广元市| 金华市| 凤山县| 北宁市| 永城市| 资中县| 北海市| 宜都市| 镇坪县| 太原市| 石河子市| 大埔县| 潞城市| 文安县| 临潭县| 石林| 都江堰市| 万山特区| 武宣县| 台州市| 黄骅市| 波密县| 定陶县| 盐城市| 海林市| 绿春县| 厦门市| 永登县| 盘山县| 卫辉市| 盐边县| 平定县| 宁陵县| 曲水县| 盘锦市| 孟津县| 邵东县| 满洲里市|