深度系統(tǒng)監(jiān)視器原理剖析

為什么要做深度系統(tǒng)監(jiān)視器卢肃?

為了達(dá)到深度操作系統(tǒng)UI/UX大統(tǒng)一的‘雄偉’目標(biāo)疲迂,閑來無事寫了深度系統(tǒng)監(jiān)視器星压,先發(fā)一張圖鎮(zhèn)樓,哈哈哈鬼譬。

深度截圖_桌面_20170722202445.png

社區(qū)的開源愛好者馬上會跳出來說,深度你們又造輪子逊脯,你們造的輪子比Gnome和KDE的系統(tǒng)監(jiān)視器好嗎优质?

回答當(dāng)然是肯定的,如果不秒殺Gnome的系統(tǒng)監(jiān)視器我造它干嘛呢军洼?

深度系統(tǒng)監(jiān)視器首先要解決的問題是:提高用戶操作的易用性
Gnome的系統(tǒng)監(jiān)視器默認(rèn)分開了三個標(biāo)簽巩螃,把進(jìn)程列表和資源總覽分開了,看進(jìn)程列表的狀態(tài)就不知道資源總覽的信息匕争,看資源總覽信息就不知道進(jìn)程列表的狀態(tài)避乏,而且還在第三個標(biāo)簽提供了一個雞肋的磁盤設(shè)備的空間,只能看啥操作都不能做甘桑,還不如放在文件管理器或者磁盤管理工具里面拍皮。


深度截圖_桌面_20170722202945.png
深度截圖_桌面_20170722203015.png

所以針對這些不爽的設(shè)計,深度系統(tǒng)監(jiān)視器把資源總覽信息和列表的進(jìn)程信息放在一起跑杭,一眼就可以知道現(xiàn)在電腦的整體負(fù)載铆帽,而且用戶馬上就可以在右邊查看高資源占用的進(jìn)程,再也不用來回費勁的切換標(biāo)簽去看這兩個本來就應(yīng)該在一起的信息德谅。

其次爹橱,深度系統(tǒng)監(jiān)視器狠下內(nèi)功,不但可以對每個進(jìn)程的CPU窄做、內(nèi)存狀態(tài)進(jìn)行監(jiān)控愧驱,還可以實時查看每個進(jìn)程的磁盤IO操作和網(wǎng)絡(luò)操作,一眼就知道哪些進(jìn)程在狂寫硬盤和在后臺偷偷下載了椭盏。

最后组砚,還提供一些小貼心的功能,比如查找進(jìn)程啟動命令所在的位置(甚至包括Wine程序都可以輕松找到)和類似xkill的功能(點哪殺哪)庸汗。

技術(shù)原理剖析

今天主要和大家分享一下對進(jìn)程信息進(jìn)行監(jiān)聽的原理和實現(xiàn)惫确。

在Linux中,計算機(jī)的所有數(shù)據(jù)的計算都通過讀取分析 /proc 文件系統(tǒng)來實現(xiàn)蚯舱,Linux內(nèi)核會實時把硬件的狀況更新到 /proc 這個內(nèi)存文件系統(tǒng)中改化。

下面我們就針對 /proc 不同的部分進(jìn)行原理和技術(shù)實現(xiàn)剖析:

計算總CPU數(shù)據(jù)

得到計算機(jī)的總CPU數(shù)據(jù)比較簡單,直接讀取 /proc/stat 文件即可枉昏,比如在終端中執(zhí)行命令

cat /proc/stat

即可得到下面的輸出:

cpu  492210 2258 105266 10955786 187778 0 4761 0 0 0
cpu0 89150 320 19660 1276610 76551 0 3102 0 0 0
cpu1 41252 241 10276 1403886 14385 0 350 0 0 0
cpu2 76932 339 16603 1343444 27821 0 678 0 0 0
cpu3 43124 243 8810 1408410 11182 0 143 0 0 0
cpu4 78072 320 16240 1350574 20070 0 215 0 0 0
cpu5 43333 244 8818 1407601 11858 0 77 0 0 0
cpu6 76218 318 15967 1355511 17012 0 139 0 0 0
cpu7 44126 230 8890 1409746 8896 0 55 0 0 0
intr 29214339 19 61901 0 0 0 0 0 0 1 8821 0 0 9987 0 0 0 29 0 1 0 0 0 0 35 0 0 22 7383 136 199974 241715 561627 23 546066 397 746 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 92198094
btime 1500712714
processes 36267
procs_running 4
procs_blocked 0
softirq 13122555 137 4225618 144 671154 238659 0 62530 4265310 0 3659003

第一行就是總CPU的占用率陈肛,下面的CPU1、CPU2等等表示多核CPU某個核的CPU占有率兄裂。
第一行從左到右的數(shù)值分別表示:user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice (這些值分別代表的意義在man手冊里面都有講句旱,今天就不展開了)
計算總CPU的占有率阳藻,首先要計算 workTime和totalTime:

workTime = user + nice + system;
totalTime =  return user + nice + system + idle + iowait + irq + softirq + steal;

比如我們系統(tǒng)監(jiān)視器2秒中獲取一些當(dāng)前CPU時間的切片,最后計算CPU占有率的公式就是:

cpuPercent = (currentWorkTime - prevWorkTime) * 100.0 / (currentTotalTime - prevTotalTime)

currentWorkTime和currentTotalTime表示當(dāng)前的CPU時間
prevWorkTime和prevTotalTime表示2秒前的CPU時間

取得CPU時間的代碼實現(xiàn)如下:

    unsigned long long getTotalCpuTime(unsigned long long &workTime)
    {
        FILE* file = fopen("/proc/stat", "r");
        if (file == NULL) {
            perror("Could not open stat file");
            return 0;
        }

        char buffer[1024];
        unsigned long long user = 0, nice = 0, system = 0, idle = 0;
        // added between Linux 2.5.41 and 2.6.33, see man proc(5)
        unsigned long long iowait = 0, irq = 0, softirq = 0, steal = 0, guest = 0, guestnice = 0;

        char* ret = fgets(buffer, sizeof(buffer) - 1, file);
        if (ret == NULL) {
            perror("Could not read stat file");
            fclose(file);
            return 0;
        }
        fclose(file);

        sscanf(buffer,
               "cpu  %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu",
               &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guestnice);

        workTime = user + nice + system;

        // sum everything up (except guest and guestnice since they are already included
        // in user and nice, see http://unix.stackexchange.com/q/178045/20626)
        return user + nice + system + idle + iowait + irq + softirq + steal;
    }

具體可以參考: https://github.com/manateelazycat/deepin-system-monitor/blob/master/src/utils.cpp

計算總內(nèi)存數(shù)據(jù)

計算機(jī)總內(nèi)存的數(shù)據(jù)保存在 /proc/meminfo 文件中谈撒,你可以通過:

cat /proc/meminfo

得到類似下面的信息:

MemTotal:        8056276 kB
MemFree:          897868 kB
MemAvailable:    4861656 kB
Buffers:          143776 kB
Cached:          4613124 kB
SwapCached:            0 kB
Active:          3455196 kB
Inactive:        3220984 kB
Active(anon):    1932732 kB
Inactive(anon):   736476 kB
Active(file):    1522464 kB
Inactive(file):  2484508 kB
Unevictable:          48 kB
Mlocked:              48 kB
SwapTotal:      36042872 kB
SwapFree:       36042872 kB
Dirty:               356 kB
Writeback:             0 kB
AnonPages:       1919408 kB
Mapped:           888900 kB
Shmem:            749924 kB
Slab:             313304 kB
SReclaimable:     262640 kB
SUnreclaim:        50664 kB
KernelStack:       12960 kB
PageTables:        55112 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    40071008 kB
Committed_AS:    8831548 kB
VmallocTotal:   34359738367 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      233872 kB
DirectMap2M:     8032256 kB
DirectMap1G:           0 kB

或者通過命令 free 得到類似的輸出:

              total        used        free      shared  buff/cache   available
Mem:        8056276     2167084      861736      756536     5027456     4826836
Swap:      36042872           0    36042872

計算內(nèi)存的占有就要簡單的很多:

memoryPercent = (total - available) * 100.0 / total

注意腥泥,當(dāng)前系統(tǒng)使用的內(nèi)存是由內(nèi)存總量total減去可用內(nèi)存aviailable的值來計算的,不能用

memoryPercent = used * 100.0 / total

因為 used 的值不包括一些被內(nèi)核占用并且永不釋放的緩存內(nèi)存啃匿,如果用 used 的方式來計算內(nèi)存百分比蛔外,會發(fā)現(xiàn)最終計算的結(jié)果會比實際占用的內(nèi)存小 15% 左右。

具體的代碼實現(xiàn)溯乒,我們用 libprocps-dev 這個開發(fā)庫提供的 meminfo() 函數(shù)夹厌,然后直接讀取 kb_main_total 和 kb_main_available 就可以了,交換空間讀取 kb_swap_used 和 kb_swap_total 這兩個變量的值裆悄。
參考代碼實現(xiàn)如下:

meminfo();

memoryPercent = (kb_main_total - kb_main_available) * 100.0 / kb_main_total
swapPercent = kb_swap_used * 100.0 / kb_swap_total

計算總網(wǎng)絡(luò)數(shù)據(jù)

計算機(jī)總網(wǎng)絡(luò)數(shù)據(jù)可以通過讀取分析 /proc/net/dev 文件來計算, 執(zhí)行命令后

cat /proc/net/dev

可以得到類似的數(shù)據(jù):

Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
enp0s25:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
    lo: 65691844  116033    0    0    0     0          0         0 65691844  116033    0    0    0     0       0          0
wlp4s0: 1249471414  907278    0    0    0     0          0         0 88564991  428056    0    0    0     0       0          0

這個文件的每一行是一個網(wǎng)絡(luò)設(shè)備矛纹,我們需要獲得網(wǎng)絡(luò)設(shè)備的第一個和第九個值,第一個值表示下載的總byte數(shù)光稼,第九個值表示上傳的總byte數(shù)或南。
原理就是讀取 /proc/net/dev 的網(wǎng)絡(luò)設(shè)備的下載和上傳數(shù)據(jù),加在一起求和钟哥,同時要排除 lo 這個虛擬網(wǎng)絡(luò)設(shè)備迎献,具體的代碼實現(xiàn)如下:

void getNetworkBandWidth(unsigned long long int &receiveBytes, unsigned long long int &sendBytes)
    {
        char *buf;
        static int bufsize;
        FILE *devfd;

        buf = (char *) calloc(255, 1);
        bufsize = 255;
        devfd = fopen("/proc/net/dev", "r");

        // Ignore the first two lines of the file.
        fgets(buf, bufsize, devfd);
        fgets(buf, bufsize, devfd);

        receiveBytes = 0;
        sendBytes = 0;

        while (fgets(buf, bufsize, devfd)) {
            unsigned long long int rBytes, sBytes;
            char *line = strdup(buf);

            char *dev;
            dev = strtok(line, ":");

            // Filter lo (virtual network device). 
            if (QString::fromStdString(dev).trimmed() != "lo") {
                sscanf(buf + strlen(dev) + 2, "%llu %*d %*d %*d %*d %*d %*d %*d %llu", &rBytes, &sBytes);

                receiveBytes += rBytes;
                sendBytes += sBytes;
            }

            free(line);
        }

        fclose(devfd);
        free(buf);
    }

這樣,我們只用每2秒計算一下內(nèi)存的總上傳和下載byte數(shù)就可以得知系統(tǒng)總共下載和上傳的帶寬腻贰,總的上傳和下載速度就更簡單了:

downloadSpeed = (currentDownloadBytes - prevDownloadBytes) / 1024.0
uploadSpeed = (currentUploadBytes - prevUploadBytes) / 1024.0

計算進(jìn)程的CPU和內(nèi)存數(shù)據(jù)

計算進(jìn)程的CPU比較簡單吁恍,類似總CPU的計算方式,只是用進(jìn)程自己的CPU值來計算播演, 進(jìn)程自己的CPU值通過讀取 /proc/pid/stat 來讀取冀瓦。
進(jìn)程的內(nèi)存信息也是從 /proc/pid/stat 文件中讀取。
具體的代碼實現(xiàn)請參考:https://github.com/manateelazycat/deepin-system-monitor/blob/master/src/status_monitor.cpp

計算進(jìn)程的磁盤IO數(shù)據(jù)

計算進(jìn)程的磁盤IO數(shù)據(jù)主要通過讀取 /proc/pid/io 文件的內(nèi)容來解析写烤, 執(zhí)行命令后

sudo cat /proc/pid/io

會得到類似以下的輸出:

rchar: 2511751
wchar: 115513
syscr: 18106
syscw: 2864
read_bytes: 0
write_bytes: 0
cancelled_write_bytes: 0

我們只用注意 rchar 和 wchar這兩個值翼闽, rchar代表進(jìn)程的寫入字符數(shù)、wchar代表進(jìn)程讀取字符數(shù)洲炊,磁盤IO就很容計算:

readKbs = (currentReadChar - prevReadChar) / 2 / 1000
writeKbs = (currentWriteChar - prevWriteChar) / 2 / 1000

具體的代碼實現(xiàn):

    bool getProcPidIO(int pid, ProcPidIO &io ) 
    {
        std::stringstream ss;
        ss << "/proc/" << pid << "/io";
        std::ifstream ifs( ss.str().c_str() );
        if ( ifs.good() ) {
            while ( ifs.good() && !ifs.eof() ) {
                std::string s;
                getline( ifs, s );
                unsigned long t;
                if ( sscanf( s.c_str(), "rchar: %lu", &t ) == 1 ) io.rchar = t;
                else if ( sscanf( s.c_str(), "wchar: %lu", &t ) == 1 ) io.wchar = t;
                else if ( sscanf( s.c_str(), "syscr: %lu", &t ) == 1 ) io.syscr = t;
                else if ( sscanf( s.c_str(), "syscw: %lu", &t ) == 1 ) io.syscw = t;
                else if ( sscanf( s.c_str(), "read_bytes: %lu", &t ) == 1 ) io.read_bytes = t;
                else if ( sscanf( s.c_str(), "write_bytes: %lu", &t ) == 1 ) io.write_bytes = t;
                else if ( sscanf( s.c_str(), "cancelled_write_bytes: %lu", &t ) == 1 ) io.cancelled_write_bytes = t;
            }
        } else {
            return false;
        }

        return true;
    }

DiskStatus StatusMonitor::getProcessDiskStatus(int pid)
{
    ProcPidIO pidIO;
    getProcPidIO(pid, pidIO);

    DiskStatus status = {0, 0};

    if (processWriteKbs->contains(pid)) {
        status.writeKbs = (pidIO.wchar - processWriteKbs->value(pid)) / (updateDuration / 1000.0);
    }
    (*processWriteKbs)[pid] = pidIO.wchar;

    if (processReadKbs->contains(pid)) {
        status.readKbs = (pidIO.rchar - processReadKbs->value(pid)) / (updateDuration / 1000.0);
    }
    (*processReadKbs)[pid] = pidIO.rchar;

    return status;
}

計算進(jìn)程的網(wǎng)絡(luò)IO數(shù)據(jù)

計算每個進(jìn)程的網(wǎng)絡(luò)IO數(shù)據(jù)比較復(fù)雜沧烈,原理步驟如下:
1自赔、獲取進(jìn)程的所有TCP鏈接的inode, /proc/pid/fd 目錄下代表當(dāng)前進(jìn)程所有打開的文件描述符,我們舉例網(wǎng)易云音樂的進(jìn)程,內(nèi)容類似:

andy@andy-PC:~/deepin-system-monitor/build$ ls -al /proc/22269/fd
總用量 0
dr-x------ 2 andy andy  0 7月  22 17:18 .
dr-xr-xr-x 9 andy andy  0 7月  22 17:11 ..
lr-x------ 1 andy andy 64 7月  22 17:18 0 -> pipe:[139620]
l-wx------ 1 andy andy 64 7月  22 17:18 1 -> /dev/null
lrwx------ 1 andy andy 64 7月  22 17:18 10 -> socket:[141559]
lrwx------ 1 andy andy 64 7月  22 17:21 100 -> socket:[564075]
lrwx------ 1 andy andy 64 7月  22 17:18 101 -> /dev/shm/.org.chromium.Chromium.NLubPR (deleted)
l-wx------ 1 andy andy 64 7月  22 17:32 102 -> /home/andy/.cache/netease-cloud-music/AlbumCover/4e3830fa33b1caf266bfe8ff6acd7634.tmp (deleted)
l-wx------ 1 andy andy 64 7月  22 17:32 103 -> /home/andy/.cache/netease-cloud-music/CachedSongs/4176388-320-7c00bba57b7a737c8047bb11bd07827d.mp3.tmp (deleted)
lrwx------ 1 andy andy 64 7月  22 17:32 104 -> socket:[751267]
lrwx------ 1 andy andy 64 7月  22 17:18 105 -> socket:[751268]
lrwx------ 1 andy andy 64 7月  22 17:21 106 -> socket:[756879]
lrwx------ 1 andy andy 64 7月  22 17:18 11 -> socket:[141560]
lrwx------ 1 andy andy 64 7月  22 17:18 110 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 111 -> /dev/shm/.org.chromium.Chromium.8IQyY6 (deleted)
lrwx------ 1 andy andy 64 7月  22 17:18 112 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/Local Storage/orpheus_orpheus_0.localstorage
lrwx------ 1 andy andy 64 7月  22 17:24 115 -> socket:[753243]
lrwx------ 1 andy andy 64 7月  22 17:18 116 -> socket:[753244]
lr-x------ 1 andy andy 64 7月  22 17:18 117 -> pipe:[753245]
lrwx------ 1 andy andy 64 7月  22 17:18 118 -> anon_inode:[eventfd]
l-wx------ 1 andy andy 64 7月  22 17:21 119 -> pipe:[753245]
lrwx------ 1 andy andy 64 7月  22 17:18 12 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 120 -> socket:[753248]
lrwx------ 1 andy andy 64 7月  22 17:18 121 -> anon_inode:[eventfd]
lr-x------ 1 andy andy 64 7月  22 17:18 122 -> /dev/urandom
lrwx------ 1 andy andy 64 7月  22 17:18 123 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 13 -> anon_inode:[eventfd]
lr-x------ 1 andy andy 64 7月  22 17:18 14 -> anon_inode:inotify
lrwx------ 1 andy andy 64 7月  22 17:18 15 -> socket:[143624]
lr-x------ 1 andy andy 64 7月  22 17:18 16 -> /usr/lib/netease-cloud-music/icudtl.dat
lr-x------ 1 andy andy 64 7月  22 17:18 17 -> /usr/lib/netease-cloud-music/snapshot_blob.bin
lr-x------ 1 andy andy 64 7月  22 17:18 18 -> /usr/lib/netease-cloud-music/natives_blob.bin
lr-x------ 1 andy andy 64 7月  22 17:18 19 -> /usr/lib/netease-cloud-music/locales/zh-CN.pak
l-wx------ 1 andy andy 64 7月  22 17:18 2 -> /dev/null
lr-x------ 1 andy andy 64 7月  22 17:18 20 -> /usr/lib/netease-cloud-music/cef.pak
lr-x------ 1 andy andy 64 7月  22 17:18 21 -> /usr/lib/netease-cloud-music/cef_100_percent.pak
lr-x------ 1 andy andy 64 7月  22 17:18 22 -> /usr/lib/netease-cloud-music/cef_200_percent.pak
lr-x------ 1 andy andy 64 7月  22 17:18 23 -> anon_inode:inotify
lr-x------ 1 andy andy 64 7月  22 17:18 24 -> /usr/lib/netease-cloud-music/cef_extensions.pak
lrwx------ 1 andy andy 64 7月  22 17:18 25 -> socket:[141561]
lrwx------ 1 andy andy 64 7月  22 17:18 26 -> socket:[141562]
lr-x------ 1 andy andy 64 7月  22 17:18 27 -> pipe:[141563]
l-wx------ 1 andy andy 64 7月  22 17:18 28 -> pipe:[141563]
lrwx------ 1 andy andy 64 7月  22 17:18 29 -> socket:[141564]
lrwx------ 1 andy andy 64 7月  22 17:18 3 -> socket:[141558]
lrwx------ 1 andy andy 64 7月  22 17:18 30 -> socket:[141605]
lr-x------ 1 andy andy 64 7月  22 17:18 31 -> pipe:[140610]
l-wx------ 1 andy andy 64 7月  22 17:18 32 -> pipe:[140610]
lrwx------ 1 andy andy 64 7月  22 17:18 33 -> anon_inode:[eventpoll]
lrwx------ 1 andy andy 64 7月  22 17:18 34 -> socket:[137178]
lrwx------ 1 andy andy 64 7月  22 17:18 35 -> socket:[137179]
lr-x------ 1 andy andy 64 7月  22 17:18 36 -> pipe:[137180]
l-wx------ 1 andy andy 64 7月  22 17:18 37 -> pipe:[137180]
lrwx------ 1 andy andy 64 7月  22 17:18 38 -> socket:[137181]
lr-x------ 1 andy andy 64 7月  22 17:18 39 -> anon_inode:inotify
lrwx------ 1 andy andy 64 7月  22 17:18 4 -> anon_inode:[eventfd]
lr-x------ 1 andy andy 64 7月  22 17:18 40 -> pipe:[137189]
l-wx------ 1 andy andy 64 7月  22 17:18 41 -> pipe:[137189]
lrwx------ 1 andy andy 64 7月  22 17:18 42 -> anon_inode:[eventpoll]
lrwx------ 1 andy andy 64 7月  22 17:18 43 -> socket:[137190]
lrwx------ 1 andy andy 64 7月  22 17:18 44 -> socket:[137191]
lr-x------ 1 andy andy 64 7月  22 17:18 45 -> pipe:[137192]
l-wx------ 1 andy andy 64 7月  22 17:18 46 -> pipe:[137192]
lrwx------ 1 andy andy 64 7月  22 17:18 47 -> anon_inode:[eventpoll]
lrwx------ 1 andy andy 64 7月  22 17:18 48 -> anon_inode:[eventpoll]
lrwx------ 1 andy andy 64 7月  22 17:18 49 -> socket:[131820]
lrwx------ 1 andy andy 64 7月  22 17:18 5 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 50 -> socket:[131821]
lrwx------ 1 andy andy 64 7月  22 17:18 51 -> socket:[137193]
lrwx------ 1 andy andy 64 7月  22 17:18 52 -> socket:[137194]
lr-x------ 1 andy andy 64 7月  22 17:18 53 -> pipe:[137195]
l-wx------ 1 andy andy 64 7月  22 17:18 54 -> pipe:[137195]
lr-x------ 1 andy andy 64 7月  22 17:18 55 -> pipe:[131822]
l-wx------ 1 andy andy 64 7月  22 17:18 56 -> pipe:[131822]
lr-x------ 1 andy andy 64 7月  22 17:18 57 -> pipe:[140611]
l-wx------ 1 andy andy 64 7月  22 17:18 58 -> pipe:[140611]
lrwx------ 1 andy andy 64 7月  22 17:18 59 -> socket:[140614]
lrwx------ 1 andy andy 64 7月  22 17:18 6 -> socket:[138694]
lrwx------ 1 andy andy 64 7月  22 17:18 60 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 61 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 62 -> socket:[137196]
lr-x------ 1 andy andy 64 7月  22 17:18 63 -> /dev/urandom
lrwx------ 1 andy andy 64 7月  22 17:18 64 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 65 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/Visited Links
lrwx------ 1 andy andy 64 7月  22 17:18 66 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/Cookies
l-wx------ 1 andy andy 64 7月  22 17:18 67 -> /home/andy/.cache/netease-cloud-music/Logs/webview.log
l-wx------ 1 andy andy 64 7月  22 17:18 68 -> /home/andy/.cache/netease-cloud-music/Logs/web-statis.log
lrwx------ 1 andy andy 64 7月  22 17:18 69 -> socket:[143663]
lr-x------ 1 andy andy 64 7月  22 17:18 7 -> anon_inode:inotify
lrwx------ 1 andy andy 64 7月  22 17:18 70 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 71 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 72 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 73 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 74 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 75 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 76 -> /home/andy/.pki/nssdb/cert9.db
lrwx------ 1 andy andy 64 7月  22 17:18 77 -> /home/andy/.pki/nssdb/key4.db
lrwx------ 1 andy andy 64 7月  22 17:18 78 -> /home/andy/.config/netease-cloud-music/OfflineLibrary.db
lr-x------ 1 andy andy 64 7月  22 17:18 79 -> anon_inode:inotify
lrwx------ 1 andy andy 64 7月  22 17:18 8 -> anon_inode:[eventfd]
lrwx------ 1 andy andy 64 7月  22 17:18 80 -> /home/andy/.config/netease-cloud-music/OfflineLibrary.db
lrwx------ 1 andy andy 64 7月  22 17:18 81 -> socket:[141607]
lrwx------ 1 andy andy 64 7月  22 17:18 82 -> socket:[141608]
lr-x------ 1 andy andy 64 7月  22 17:18 83 -> anon_inode:inotify
lrwx------ 1 andy andy 64 7月  22 17:18 84 -> /home/andy/.config/netease-cloud-music/OnlineLibrary.db
lrwx------ 1 andy andy 64 7月  22 17:18 85 -> /dev/dri/card1
lrwx------ 1 andy andy 64 7月  22 17:18 86 -> socket:[140638]
lrwx------ 1 andy andy 64 7月  22 17:18 87 -> socket:[754448]
lrwx------ 1 andy andy 64 7月  22 17:18 88 -> socket:[140640]
lrwx------ 1 andy andy 64 7月  22 17:18 89 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/index
lrwx------ 1 andy andy 64 7月  22 17:18 9 -> socket:[143622]
lr-x------ 1 andy andy 64 7月  22 17:18 90 -> /dev/shm/.org.chromium.Chromium.hjPeWY (deleted)
lrwx------ 1 andy andy 64 7月  22 17:18 91 -> /dev/shm/.org.chromium.Chromium.hjPeWY (deleted)
lrwx------ 1 andy andy 64 7月  22 17:18 92 -> /dev/shm/.org.chromium.Chromium.gSxJ28 (deleted)
lrwx------ 1 andy andy 64 7月  22 17:18 93 -> /dev/shm/.org.chromium.Chromium.zxWAqZ (deleted)
lrwx------ 1 andy andy 64 7月  22 17:18 94 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/data_0
lrwx------ 1 andy andy 64 7月  22 17:18 95 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/data_1
lrwx------ 1 andy andy 64 7月  22 17:18 96 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/data_2
lrwx------ 1 andy andy 64 7月  22 17:18 97 -> /home/andy/.cache/netease-cloud-music/Cef/Cache/data_3
lr-x------ 1 andy andy 64 7月  22 17:21 98 -> /home/andy/.cache/netease-cloud-music/Logs/web-statis-tmp.log.zip
lrwx------ 1 andy andy 64 7月  22 17:18 99 -> anon_inode:[eventfd]

注意那些以 socket:[number] 的文件描述符等脂, 這些socket開頭的文件描述符就對應(yīng)一個 TCP 鏈接淋叶,后面的數(shù)字就代表鏈接對應(yīng)的 TCP inode躏仇。
2牙躺、列出系統(tǒng)中 TCP inode 對應(yīng)的鏈接信息,通過命令

cat /proc/net/tcp

可以得到當(dāng)前 TCP inode 對應(yīng)的鏈接信息列表唧领,內(nèi)容類似:

  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
   0: 00000000:008B 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 25701 1 ffff89bc3387a7c0 100 0 0 10 0                     
   1: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 21108 1 ffff89bc2d91e7c0 100 0 0 10 0                     
   2: 0100007F:0438 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 118922 1 ffff89bb9e326040 100 0 0 10 0                    
   3: 00000000:01BD 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 25700 1 ffff89bc3387a040 100 0 0 10 0                     
   4: DEC7A8C0:9E7E AFFD3267:20C4 01 00000000:00000000 00:00000000 00000000  1000        0 760162 1 ffff89bb9e208000 37 4 9 10 12                    
   5: 0100007F:0438 0100007F:D934 01 00000000:00000000 00:00000000 00000000  1000        0 760161 1 ffff89ba60483000 20 4 4 10 -1                    
   6: DEC7A8C0:D9A0 06C7FCDF:1773 01 00000000:00000000 02:00000B05 00000000  1000        0 564075 2 ffff89bb5aced7c0 22 4 29 10 -1                   
   7: DEC7A8C0:9E82 AFFD3267:20C4 01 00000000:00000000 00:00000000 00000000  1000        0 758410 1 ffff89ba0fa4f7c0 57 4 32 18 13                   
   8: DEC7A8C0:B9B8 7DFD1EC0:01BB 01 00000000:00000000 02:00000359 00000000  1000        0 660202 2 ffff89ba989fa800 58 4 25 10 -1                   
   9: 0100007F:D938 0100007F:0438 01 00000000:00000000 02:000006AA 00000000  1000        0 759339 2 ffff89ba6582d800 20 4 30 10 -1                   
  10: DEC7A8C0:A9D6 42522477:0050 01 00000000:00063C60 00:00000000 00000000  1000        0 759725 1 ffff89bbb8d87080 20 4 0 10 -1                    
  11: DEC7A8C0:CAF0 3C011434:01BB 01 00000000:00000000 02:0000089A 00000000  1000        0 564925 2 ffff89bb7c5fd800 72 4 30 10 -1                   
  12: DEC7A8C0:CFC6 DADCDF36:01BB 01 00000000:00000000 02:00000EB7 00000000  1000        0 565035 2 ffff89bb071aa040 22 4 28 10 -1                   
  13: 0100007F:0438 0100007F:D938 01 00000000:00000000 00:00000000 00000000  1000        0 758409 1 ffff89ba6582d080 21 4 20 10 -1                   
  14: 0100007F:D934 0100007F:0438 01 00000000:00000000 02:000002F2 00000000  1000        0 759243 2 ffff89bb6d885080 20 4 11 10 -1                   
  15: DEC7A8C0:E168 5D39C834:01BB 01 00000000:00000000 02:000005E2 00000000  1000        0 565468 2 ffff89bbb8d0f780 57 4 30 10 -1                   
  16: DEC7A8C0:D898 C5A06F3B:0050 08 00000000:00000001 02:00000734 00000000  1000        0 761352 2 ffff89ba989fa080 22 4 24 10 -1                   
  17: DEC7A8C0:93B2 5B822879:1F92 01 00000000:00000000 02:00000226 00000000  1000        0 566332 2 ffff89bb05284780 22 4 28 10 -1          

3藻雌、使用 libcap 抓包的方法雌续,計算出每個TCP鏈接對應(yīng)的網(wǎng)絡(luò)流量后,然后反向通過步驟一的 pid <-> inode list 信息胯杭,最后計算出每個進(jìn)程的網(wǎng)絡(luò)流量驯杜。

這么繞的計算方法,我覺得這個世間早以有牛人實現(xiàn)了做个,Google了一下艇肴,發(fā)現(xiàn)可以直接使用 libnethogs 這個庫來計算每個進(jìn)程的網(wǎng)絡(luò)流量。

具體的代碼實現(xiàn)可以參考:https://github.com/manateelazycat/deepin-system-monitor/blob/master/src/network_traffic_filter.cpp

nethogs這種方式只能計算出TCP鏈接的網(wǎng)絡(luò)流量叁温,無法計算UDP鏈接的網(wǎng)絡(luò)流量,當(dāng)然TCP流量分析已經(jīng)滿足了大部分應(yīng)用的需求核畴。

Wine程序的網(wǎng)絡(luò)流量

上面說的方法適用于Linux原生應(yīng)用的網(wǎng)絡(luò)進(jìn)城監(jiān)控膝但,但是無法直接通過監(jiān)聽Wine程序的網(wǎng)絡(luò)流量,比如基于Wine運(yùn)行的迅雷谤草。
當(dāng)一個Wine程序需要進(jìn)行網(wǎng)絡(luò)上傳和下載的時候跟束, Wine會后臺分配一個 wineserver.real 的進(jìn)程來完成Wine程序網(wǎng)絡(luò)代理的功能,wineserver.real 獲得網(wǎng)絡(luò)數(shù)據(jù)以后再傳遞給 Wine 程序丑孩。

所以計算Wine程序的網(wǎng)絡(luò)流量的步驟是:
1冀宴、統(tǒng)計所有Wine程序運(yùn)行時的環(huán)境變量,通過 GIO_LAUNCHED_DESKTOP_FILE 這個環(huán)境變量獲得 Wine 程序的 desktop 文件路徑温学;
2略贮、統(tǒng)計所有 wineserver.real 進(jìn)程的環(huán)境變量,通過 GIO_LAUNCHED_DESKTOP_FILE 這個環(huán)境變量獲得wineserver.real 進(jìn)程 的 desktop 文件路徑仗岖;
3逃延、分析系統(tǒng)的TCP鏈接后,找到匹配的 wineserver.real 的pid;
4轧拄、通過 wineserver.real 匹配的 desktop 文件找到對應(yīng)的 Wine 程序的 pid;
5揽祥、最后,用 wineserver.real 進(jìn)程的網(wǎng)絡(luò)流量數(shù)據(jù)替換 Wine 程序的網(wǎng)絡(luò)流量檩电,并清空 wineserver.real 進(jìn)程的網(wǎng)絡(luò)流量拄丰;

這樣用戶就可以在系統(tǒng)監(jiān)視器看到Wine程序的網(wǎng)絡(luò)流量了,如下圖所示:

深度截圖_選擇區(qū)域_20170725110657.png

代碼參考實現(xiàn):

std::string getDesktopFileFromName(int pid, QString procName, QString cmdline)
    {
        if (desktopfileMaps.contains(cmdline)) {
            return desktopfileMaps[cmdline].toStdString();
        } else {
            // Need found desktop file from process environment, if process is wine program.
            if (cmdline.startsWith("c:\\")) {
                QString gioDesktopFile = Utils::getProcessEnvironmentVariable(pid, "GIO_LAUNCHED_DESKTOP_FILE");

                return gioDesktopFile.toStdString();
            } else {
                QDirIterator dir("/usr/share/applications", QDirIterator::Subdirectories);
                std::string desktopFile;

                // Convert to lower characters.
                QString procname = procName.toLower();

                // Replace "_" instead "-", avoid some applications desktop file can't found, such as, sublime text.
                procname.replace("_", "-");

                // Concat desktop file.
                QString processFilename = procname + ".desktop";

                if (GUI_BLACKLIST_MAP.find(procname) == GUI_BLACKLIST_MAP.end()) {
                    while(dir.hasNext()) {
                        if (dir.fileInfo().suffix() == "desktop") {
                            if (dir.fileName().toLower().contains(processFilename)) {
                                desktopFile = dir.filePath().toStdString();
                                break;
                            }
                        }
                        dir.next();
                    }
                }

                return desktopFile;
            }
        }
    }

wineApplicationDesktopMaps->clear();
    wineServerDesktopMaps->clear();

    for (auto &i:processes) {
        int pid = (&i.second)->tid;
        QString cmdline = Utils::getProcessCmdline(pid);
        bool isWineProcess = cmdline.startsWith("c:\\");
        QString name = getProcessName(&i.second, cmdline);
        QString user = (&i.second)->euser;
        double cpu = (*processCpuPercents)[pid];

        std::string desktopFile = getDesktopFileFromName(pid, name, cmdline);
        QString title = findWindowTitle->getWindowTitle(pid);
        
        bool isGui = (title != "");

        // Record wine application and wineserver.real desktop file.
        // We need transfer wineserver.real network traffic to the corresponding wine program.
        if (name == "wineserver.real") {
            // Insert pid<->desktopFile to map to search in all network process list.
            QString gioDesktopFile = Utils::getProcessEnvironmentVariable(pid, "GIO_LAUNCHED_DESKTOP_FILE");
            if (gioDesktopFile != "") {
                (*wineServerDesktopMaps)[pid] = gioDesktopFile;
            }
        } else {
            // Insert desktopFile<->pid to map to search in all network process list.
            // If title is empty, it's just a wine program, but not wine GUI window.
            if (isWineProcess && title != "") {
                (*wineApplicationDesktopMaps)[QString::fromStdString(desktopFile)] = pid;
            }
        }
        
        if (isGui) {
            guiProcessNumber++;
        } else {
            systemProcessNumber++;
        }

        bool appendItem = false;
        if (filterType == OnlyGUI) {
            appendItem = (user == currentUsername && isGui);
        } else if (filterType == OnlyMe) {
            appendItem = (user == currentUsername);
        } else if (filterType == AllProcess) {
            appendItem = true;
        }

        if (appendItem) {
            if (title == "") {
                if (isWineProcess) {
                    // If wine process's window title is blank, it's not GUI window process.
                    // Title use process name instead.
                    title = name;
                } else {
                    title = getDisplayNameFromName(name, desktopFile);
                }
            }
            QString displayName;
            if (filterType == AllProcess) {
                displayName = QString("[%1] %2").arg(user).arg(title);
            } else {
                displayName = title;
            }

            long memory = ((&i.second)->resident - (&i.second)->share) * sysconf(_SC_PAGESIZE);

            QPixmap icon;
            if (desktopFile.size() == 0) {
                icon = findWindowTitle->getWindowIcon(findWindowTitle->getWindow(pid), 24);
            } else {
                icon = getDesktopFileIcon(desktopFile, 24);
            }

            ProcessItem *item = new ProcessItem(icon, name, displayName, cpu, memory, pid, user, (&i.second)->state);
            items << item;
        } else {
            // Fill GUI processes information for continue merge action.
            if (filterType == OnlyGUI) {
                if (childInfoMap.contains(pid)) {
                    long memory = ((&i.second)->resident - (&i.second)->share) * sysconf(_SC_PAGESIZE);
                    childInfoMap[pid].cpu = cpu;
                    childInfoMap[pid].memory = memory;
                }
            }
        }
    }

    // Transfer wineserver.real network traffic to the corresponding wine program.
    QMap<int, NetworkStatus>::iterator i;
    for (i = networkStatusSnapshot.begin(); i != networkStatusSnapshot.end(); ++i) {
        if (wineServerDesktopMaps->contains(i.key())) {
            QString wineDesktopFile = (*wineServerDesktopMaps)[i.key()];
            
            if (wineApplicationDesktopMaps->contains(wineDesktopFile)) {
                // Transfer wineserver.real network traffic to the corresponding wine program.
                int wineApplicationPid = (*wineApplicationDesktopMaps)[wineDesktopFile];
                networkStatusSnapshot[wineApplicationPid] = networkStatusSnapshot[i.key()];
                
                // Reset wineserver network status to zero.
                NetworkStatus networkStatus = {0, 0, 0, 0};
                networkStatusSnapshot[i.key()] = networkStatus;
            }
        }
    }

找到進(jìn)程對應(yīng)的名字

主要的名字主要有三種形式:圖形窗口的標(biāo)題俐末、Desktop文件對應(yīng)的本地化名稱和最后的命令行名稱料按。

圖形窗口的標(biāo)題的步驟是:
1、通過XCB分析 _NET_CLIENT_LIST_STACKING 數(shù)據(jù)列出所有圖形窗口的XID
2鹅搪、通過XCB分析 _NET_WM_PID 得出每個窗口對應(yīng)的 pid
3站绪、然后對比圖形窗口的 pid list 和 process pid list, 找到所有圖形窗口的 pid list
4、最后通過XCB分析 xid 對應(yīng)的 _NET_WM_NAME 來查找圖形窗口的標(biāo)題

具體的代碼實現(xiàn)參考:

QList<xcb_window_t> WindowManager::getWindows()
{
    QList<xcb_window_t> windows;
    xcb_get_property_reply_t *listReply = getProperty(rootWindow, "_NET_CLIENT_LIST_STACKING", XCB_ATOM_WINDOW);

    if (listReply) {
        xcb_window_t *windowList = static_cast<xcb_window_t*>(xcb_get_property_value(listReply));
        int windowListLength = listReply->length;

        for (int i = 0; i < windowListLength; i++) {
            xcb_window_t window = windowList[i];

            foreach(QString type, getWindowTypes(window)) {
                if (type == "_NET_WM_WINDOW_TYPE_NORMAL" ||
                    type == "_NET_WM_WINDOW_TYPE_DIALOG"
                    ) {
                    bool needAppend = false;

                    QStringList states = getWindowStates(window);
                    if (states.length() == 0 ||
                        (!states.contains("_NET_WM_STATE_HIDDEN"))) {
                        if (getWindowWorkspace(window) == getCurrentWorkspace(rootWindow)) {
                            needAppend = true;
                        }
                    }

                    if (needAppend) {
                        windows.append(window);
                        break;
                    }
                }
            }
        }

        free(listReply);

        // We need re-sort windows list from up to bottom,
        // to make compare cursor with window area from up to bottom.
        std::reverse(windows.begin(), windows.end());

        // Add desktop window.
        windows.append(rootWindow);

        // Just use for debug.
        // foreach (auto window, windows) {
        //     qDebug() << getWindowName(window);
        // }
    }

    return windows;
}

int WindowManager::getWindowPid(xcb_window_t window)
{
    xcb_get_property_reply_t *reply = getProperty(window, "_NET_WM_PID", XCB_ATOM_CARDINAL);
    int pid = 0;

    if (reply) {
        pid = *((int *) xcb_get_property_value(reply));

        free(reply);
    }

    return pid;
}

QString WindowManager::getWindowName(xcb_window_t window)
{
    if (window == rootWindow) {
        return tr("Desktop");
    } else {
        xcb_get_property_reply_t *reply = getProperty(window, "_NET_WM_NAME", getAtom("UTF8_STRING"));

        if (reply) {
            QString result = QString::fromUtf8(static_cast<char*>(xcb_get_property_value(reply)), xcb_get_property_value_length(reply));

            free(reply);

            return result;
        } else {
            return QString();
        }
    }
}

得到Desktop對應(yīng)的名稱原理:
1丽柿、通過讀取 /proc/pid/cmdline 得到進(jìn)程對應(yīng)的啟動命令行
2恢准、提取 cmdline 第一個參數(shù)得到啟動命令
3魂挂、通過啟動命令在 /usr/share/applications 目錄下查找對應(yīng)的 *.desktop 文件
4、分析文件的 Name[locale] 字符串馁筐,得到本地化的名字涂召, locale 在中文表示 zh_CN

具體的代碼實現(xiàn)參考:

QString getProcessCmdline(pid_t pid)
    {
        std::string temp;
        try {
            std::fstream fs;
            fs.open("/proc/"+std::to_string((long)pid)+"/cmdline", std::fstream::in);
            std::getline(fs,temp);
            fs.close();
        } catch(std::ifstream::failure e) {
            return "FAILED TO READ PROC";
        }

        // change \0 to ' '
        std::replace(temp.begin(),temp.end(),'\0',' ');

        if (temp.size()<1) {
            return "";
        }

        return QString::fromStdString(temp).trimmed();
    }

QString getProcessNameFromCmdLine(const pid_t pid)
    {
        std::string cmdline = getProcessCmdline(pid).toStdString();

        if (cmdline.size()<1) {
            return "";
        }

        // Maintain linux paths.
        std::replace(cmdline.begin(),cmdline.end(),'\\','/');

        // Get cmdline arguments and first argument name.
        auto args = explode(cmdline, ' ');
        QString name = QFileInfo(QString::fromStdString(args[0])).fileName();

        // Get first argument that start with '/' if first argument is script program, such as 'python'.
        auto pos = SCRIPT_PROGRAM_MAP.find(name);
        if (pos != SCRIPT_PROGRAM_MAP.end() && args.size() > 1) {
            for (unsigned int i = 1; i < args.size(); i++) {
                QString argument = QString::fromStdString(args[i]);

                // Return first argument that start with '/'.
                if (argument.startsWith("/")) {
                    return QFileInfo(argument).fileName();
                }
            }

            for (unsigned int j = 1; j < args.size(); j++) {
                QString argument = QString::fromStdString(args[j]);

                // Return first argument that not start with '-'.
                if (!argument.startsWith("-")) {
                    return QFileInfo(argument).fileName();
                }
            }
        }

        return name;
    }

QString getDisplayNameFromName(QString procName, std::string desktopFile, bool displayProcessName)
    {
        QString procname = procName.toLower();
        if (processDescriptions.contains(procname)) {
            if (displayProcessName) {
                return QString("%1    ( %2 )").arg(processDescriptions[procname], procName);
            } else {
                return processDescriptions[procname];
            }
        }

        if (desktopFile.size() == 0) {
            return procName;
        }

        std::ifstream in;
        in.open(desktopFile);
        QString displayName = procName;
        while(!in.eof()) {
            std::string line;
            std::getline(in,line);

            QString lineContent = QString::fromStdString(line);

            QString localNameFlag = QString("Name[%1]=").arg(QLocale::system().name());
            QString nameFlag = "Name=";
            QString genericNameFlag = QString("GenericName[%1]=").arg(QLocale::system().name());

            if (lineContent.startsWith(localNameFlag)) {
                displayName = lineContent.remove(0, localNameFlag.size());

                break;
            } else if (lineContent.startsWith(genericNameFlag)) {
                displayName = lineContent.remove(0, genericNameFlag.size());

                break;
            } else if (lineContent.startsWith(nameFlag)) {
                displayName = lineContent.remove(0, nameFlag.size());

                continue;
            } else {
                continue;
            }
        }
        in.close();

        return displayName;
    }

第三種進(jìn)程名的方式已經(jīng)在函數(shù) getProcessNameFromCmdLine 的代碼實現(xiàn)中體現(xiàn)了。

找到進(jìn)程二進(jìn)制文件所在位置

進(jìn)程管理中除了查看進(jìn)程的狀態(tài)敏沉,對進(jìn)程進(jìn)行簡單的結(jié)束/暫停管理操作以外果正,最重要的附加功能就是,我們要知道這個軟件到底是哪個命令啟動的盟迟,這個命令所在的目錄秋泳,這樣對于我們徹底了解進(jìn)程背后的軟件非常有幫助。

深度系統(tǒng)監(jiān)視器實現(xiàn)了一個 ”查找命令所在位置“ 的右鍵功能攒菠,這個功能的實現(xiàn)分為兩種情況:
1迫皱、Linux原生應(yīng)用進(jìn)程
2、Wine進(jìn)程

Linux原生應(yīng)用進(jìn)程查找二進(jìn)制的步驟:
1辖众、讀取 /proc/pid/cmdline 得到命令行參數(shù)
2卓起、獲取 cmdline 第一個參數(shù),即命令行路徑
3凹炸、通過 which 命令來查詢命令行的絕對路徑

Wine進(jìn)程查找二進(jìn)制的步驟:
1戏阅、讀取 /proc/pid/cmdline 得到命令行參數(shù),一般是 c:\xxxxxxx\xxxx.exe 的Windows路徑
2啤它、轉(zhuǎn)換 Windows 路徑為: drive_c/xxxxxxx/xxx.exe 的Linux相對路徑
3奕筐、通過讀取 /proc/pid/environ 路徑,找到進(jìn)程的環(huán)境變量列表
4变骡、進(jìn)一步找到 WINEPREFIX 對應(yīng)的值救欧,一般是 ~/.deepinwine/xxx 的軟件安裝目錄形式
5、最后鏈接軟件安裝目錄+相對路徑的形式锣光,得到Wine進(jìn)程啟動命令的絕對路徑笆怠,一般是 ~/.deepinwine/xxx/drive_c/xxx/xxx.exe的形式

具體代碼參考實現(xiàn):

void ProcessManager::openProcessDirectory()
{
    for (int pid : *actionPids) {
        QString cmdline = Utils::getProcessCmdline(pid);
        if (cmdline.size() > 0) {
            // Found wine program location if cmdline starts with c://.
            if (cmdline.startsWith("c:\\")) {
                QString winePrefix = Utils::getProcessEnvironmentVariable(pid, "WINEPREFIX");
                cmdline = cmdline.replace("\\", "/").replace("c:/", "/drive_c/");
                
                DDesktopServices::showFileItem(winePrefix + cmdline);
            }
            // Else find program location through 'which' command.
            else {
                cmdline = cmdline.split(QRegExp("\\s")).at(0);
            
                QProcess whichProcess;
                QString exec = "which";
                QStringList params;
                params << cmdline;
                whichProcess.start(exec, params);
                whichProcess.waitForFinished();
                QString output(whichProcess.readAllStandardOutput());

                QString processPath = output.split("\n")[0];
                DDesktopServices::showFileItem(processPath);
            }
            
        }
    }

    actionPids->clear();
}

QString getProcessEnvironmentVariable(pid_t pid, QString environmentName)
    {
        std::string temp;
        try {
            std::fstream fs;
            fs.open("/proc/"+std::to_string((long)pid)+"/environ", std::fstream::in);
            std::getline(fs,temp);
            fs.close();
        } catch(std::ifstream::failure e) {
            return "FAILED TO READ PROC";
        }

        // change \0 to ' '
        std::replace(temp.begin(),temp.end(),'\0','\n');

        if (temp.size()<1) {
            return "";
        }

        foreach (auto environmentVariable, QString::fromStdString(temp).trimmed().split("\n")) {
            if (environmentVariable.startsWith(environmentName)) {
                return environmentVariable.remove(0, QString("%1=").arg(environmentName).length());
            }
        }

        return "";
    }

用setcap替換setuid的方式給予讀取系統(tǒng)目錄的權(quán)限

上面講解了深度系統(tǒng)監(jiān)視器的核心模塊的原理和代碼參考實現(xiàn),我們會發(fā)現(xiàn)大部分都要讀取系統(tǒng)目錄 /proc, /proc這個目錄的大部分內(nèi)容只有root用戶才有權(quán)限讀取誊爹。

很多初學(xué)者喜歡用setuid的方式直接賦予二進(jìn)制root權(quán)限蹬刷,但是這樣非常危險,會造成圖形前端獲得過大的權(quán)限频丘,從而產(chǎn)生安全漏洞办成。

Linux內(nèi)核針對這種情況有更好的實現(xiàn)方式,用 setcap 給予二進(jìn)制特定的權(quán)限搂漠,保證二進(jìn)制的特殊權(quán)限在最小的范圍中迂卢,比如在深度系統(tǒng)監(jiān)視器中就用命令:

sudo setcap cap_kill,cap_net_raw,cap_dac_read_search,cap_sys_ptrace+ep ./deepin-system-monitor

來給予進(jìn)程相應(yīng)的能力,比如:

  • cap_net_raw 對應(yīng)網(wǎng)絡(luò)文件讀取權(quán)限
  • cap_dac_read_search 對應(yīng)文件讀取檢查權(quán)限
  • cap_sys_ptrace 對應(yīng)進(jìn)程內(nèi)存信息讀取權(quán)限
    這樣,在保證二進(jìn)制有對應(yīng)讀取權(quán)限的同時而克,又保證了二進(jìn)制最小化的權(quán)限范圍靶壮,最大化的保證了應(yīng)用和系統(tǒng)的安全。

最后员萍,深度系統(tǒng)監(jiān)視器整個項目都遵守GPLv3許可證協(xié)議腾降,歡迎各位大神貢獻(xiàn)代碼: https://github.com/manateelazycat/deepin-system-monitor

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碎绎,隨后出現(xiàn)的幾起案子螃壤,更是在濱河造成了極大的恐慌,老刑警劉巖筋帖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奸晴,死亡現(xiàn)場離奇詭異,居然都是意外死亡日麸,警方通過查閱死者的電腦和手機(jī)蚁滋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赘淮,“玉大人,你說我怎么就攤上這事睦霎∩倚叮” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵副女,是天一觀的道長蛤高。 經(jīng)常有香客問我,道長碑幅,這世上最難降的妖魔是什么戴陡? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮沟涨,結(jié)果婚禮上恤批,老公的妹妹穿的比我還像新娘。我一直安慰自己裹赴,他們只是感情好喜庞,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棋返,像睡著了一般延都。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睛竣,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天晰房,我揣著相機(jī)與錄音,去河邊找鬼。 笑死殊者,一個胖子當(dāng)著我的面吹牛与境,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幽污,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼嚷辅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了距误?” 一聲冷哼從身側(cè)響起簸搞,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎准潭,沒想到半個月后趁俊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡刑然,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年寺擂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泼掠。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡怔软,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出择镇,到底是詐尸還是另有隱情挡逼,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布腻豌,位于F島的核電站家坎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吝梅。R本人自食惡果不足惜虱疏,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苏携。 院中可真熱鬧做瞪,春花似錦、人聲如沸右冻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽国旷。三九已至矛物,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跪但,已是汗流浹背履羞。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工峦萎, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忆首。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓爱榔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親糙及。 傳聞我的和親對象是個殘疾皇子详幽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經(jīng)改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數(shù)據(jù)革命閱讀 12,134評論 2 34
  • Ubuntu的發(fā)音 Ubuntu柱搜,源于非洲祖魯人和科薩人的語言迟郎,發(fā)作 oo-boon-too 的音。了解發(fā)音是有意...
    螢火蟲de夢閱讀 99,156評論 9 467
  • 如果你想知道你的服務(wù)器正在做干什么聪蘸,你就需要了解一些基本的命令宪肖,一旦你精通了這些命令,那你就是一個專業(yè)的 Linu...
    七寸知架構(gòu)閱讀 10,816評論 1 71
  • 都說一到夜里人們就有許多情緒與感慨,也許是因為在深夜健爬,上班族結(jié)束了一天的工作,學(xué)生也在忙碌的學(xué)習(xí)中得到休息和片刻的...
    小慕慕xi閱讀 1,155評論 2 2
  • “哐”的一聲控乾,他摔門而去。隨著他的離去娜遵,她的心臟好像被拎起而又被硬生生地摔在地下蜕衡,碎成了幾瓣。她心痛得簡直要窒息魔熏,...
    bolixin閱讀 356評論 0 0