亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

:Linux內存點滴 用戶進程內存空間

系統 1929 0

經常使用top命令了解進程信息,其中包括內存方面的信息。命令top幫助文檔是這么解釋各個字段的。

? ?VIRT, Virtual Image (kb)

? ?RES, Resident size (kb)

? ?SHR, Shared Mem size (kb)

? ?%MEM, Memory usage(kb)

? ?SWAP, Swapped size (kb)

? ?CODE, Code size (kb)

? ?DATA, Data+Stack size (kb)

? ?nFLT, Page Fault count

? ?nDRT, Dirty Pages count

? ?盡管有注釋,但依然感覺有些晦澀,不知所指何意?

進程內存空間

? ?正在運行的程序,叫進程。每個進程都有完全屬于自己的,獨立的,不被干擾的內存空間。此空間,被分成幾個段(Segment),分別是Text, Data, BSS, Heap, Stack。用戶進程內存空間,也是系統內核分配給該進程的VM(虛擬內存),但并不表示這個進程占用了這么多的RAM(物理內存)。這個空間有多大?命令top輸出的VIRT值告訴了我們各個進程內存空間的大小(進程內存空間隨著程序的執行會增大或者縮小)。你還可以通過/proc//maps,或者pmap -d 了解某個進程內存空間都分布,比如:

      #cat /proc/1449/maps

…

0012e000-002a4000 r-xp 00000000 08:07 3539877    /lib/i386-linux-gnu/libc-2.13.so

002a4000-002a6000 r--p 00176000 08:07 3539877    /lib/i386-linux-gnu/libc-2.13.so

002a6000-002a7000 rw-p 00178000 08:07 3539877   /lib/i386-linux-gnu/libc-2.13.so

002a7000-002aa000 rw-p 00000000 00:00 0

…

08048000-0875b000 r-xp 00000000 08:07 4072287    /usr/local/mysql/libexec/mysqld

0875b000-0875d000 r--p 00712000 08:07 4072287    /usr/local/mysql/libexec/mysqld

0875d000-087aa000 rw-p 00714000 08:07 4072287   /usr/local/mysql/libexec/mysqld

…

PS:線性地址,訪問權限, offset, 設備號,inode,映射文件
    

? ?

VM分配與釋放

? ?“內存總是被進程占用”,這句話換過來可以這么理解:進程總是需要內存。當fork()或者exec()一個進程的時候,系統內核就會分配一定量的VM給進程,作為進程的內存空間,大小由BSS段,Data段的已定義的全局變量、靜態變量、Text段中的字符直接量、程序本身的內存映像等,還有Stack段的局部變量決定。當然,還可以通過malloc()等函數動態分配內存,向上擴大heap。

? ?動態分配與靜態分配,二者最大的區別在于:1. 直到Run-Time的時候,執行動態分配,而在compile-time的時候,就已經決定好了分配多少Text+Data+BSS+Stack。2.通過malloc()動態分配的內存,需要程序員手工調用free()釋放內存,否則容易導致內存泄露,而靜態分配的內存則在進程執行結束后系統釋放(Text, Data), 但Stack段中的數據很短暫,函數退出立即被銷毀。

? ?我們使用幾個示例小程序,加深理解

      /* @filename: example-2.c */

#include <stdio.h>

 

int main(int argc, char *argv[])

{

    char arr[] = "hello world";	/* Stack段,rw--- */

    char *p = "hello world";		/* Text段,字符串直接量, r-x--  */

    arr[1] = 'l';

    *(++p) = 'l';	/* 出錯了,Text段不能write */

    return 0;

}

PS:變量p,它在Stack段,但它所指的”hello world”是一個字符串直接量,放在Text段。

 

/* @filename:example_2_2.c */

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

char *get_str_1()

{

    char str[] = "hello world";

    return str;

}

 

char *get_str_2()

{

    char *str = "hello world";

    return str;

}

 

char *get_str_3()

{

    char tmp[] = "hello world";

    char *str;

    str = (char *)malloc(12 * sizeof(char));

    memcpy(str, tmp, 12);

    return str;

}

 

int main(int argc, char *argv[])

{

    char *str_1 = get_str_1();	//出錯了,Stack段中的數據在函數退出時就銷毀了

    char *str_2 = get_str_2();	//正確,指向Text段中的字符直接量,退出程序后才會回收

    char *str_3 = get_str_3();	//正確,指向Heap段中的數據,還沒free()
      
        printf
      
      ("%s\n", str_1);
      
        printf
      
      ("%s\n", str_2);
      
        printf
      
      ("%s\n", str_3);

    if (str_3 != NULL)

    {

        free(str_3);

        str_3 = NULL;

    }

    return 0;

}

PS:函數get_str_1()返回Stack段數據,編譯時會報錯。Heap中的數據,如果不用了,應該盡早釋放free()。

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

 

char data_var  = '1';

char *mem_killer()

{

   char *p;

   p = (char *)malloc(1024*1024*4);

   memset(p, '\0', 1024*1024*4);

   p = &data_var;	//危險,內存泄露

   return p;

}

 

int main(int argc, char *argv[])

{

    char *p;

    for (;;)

    {

        p = mem_killer(); // 函數中malloc()分配的內存沒辦法free()
      
        printf
      
      ("%c\n", *p);

        sleep(20);

    }

    return 0;

}

PS:使用malloc(),特別要留意heap段中的內存不用時,盡早手工free()。通過top輸出的VIRT和RES兩值來觀察進程占用VM和RAM大小。
    

? ?本節結束之前,介紹工具size。因為Text, BSS, Data段在編譯時已經決定了進程將占用多少VM。可以通過size,知道這些信息。

# gcc example_2_3.c -o example_2_3

? ?# size example_2_3

? ?textdatabssdec ? ? hexfilename

? ?140327281683693example_2_3

? ?

malloc()

? ?編碼人員在編寫程序之際,時常要處理變化數據,無法預料要處理的數據集變化是否大(phper可能難以理解),所以除了變量之外,還需要動態分配內存。GNU libc庫提供了二個內存分配函數,分別是malloc()和calloc()。調用malloc(size_t size)函數分配內存成功,總會分配size字節VM(再次強調不是RAM),并返回一個指向剛才所分配內存區域的開端地址。分配的內存會為進程一直保留著,直到你顯示地調用free()釋放它(當然,整個進程結束,靜態和動態分配的內存都會被系統回收)。開發人員有責任盡早將動態分配的內存釋放回系統。記住一句話:盡早free()!

? ?我們來看看,malloc()小示例。

      /* @filename:example_2_4.c */

#include <stdio.h>

#include <stdlib.h>

 

int main(int argc, char *argv[])

{

    char *p_4kb, *p_128kb, *p_300kb;

    if ((p_4kb = malloc(4*1024)) != NULL)

    {

        free(p_4kb);

    }

    if ((p_128kb = malloc(128*1024)) != NULL)

    {

        free(p_128kb);

    }

    if ((p_300kb = malloc(300*1024)) != NULL)

    {

        free(p_300kb);

    }

    return 0;

}

#gcc example_2_4.c -o example_2_4

#strace -t ./example_2_4

…

00:02:53 brk(0)                         = 0x8f58000

00:02:53 brk(0x8f7a000)                 = 0x8f7a000

00:02:53 brk(0x8f79000)                 = 0x8f79000

00:02:53 mmap2(NULL, 311296, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb772d000

00:02:53 munmap(0xb772d000, 311296)     = 0

…

PS:系統調用brk(0)取得當前堆的地址,也稱為斷點。
    

? ?通過跟蹤系統內核調用,可見glibc函數malloc()總是通過brk()或mmap()系統調用來滿足內存分配需求。函數malloc(),根據不同大小內存要求來選擇brk(),還是mmap(), 128Kbytes是臨界值。小塊內存()。留意紫圈標注

? ?

? ?示意圖:函數malloc(1024*1024),大于128kbytes,在heap與stack之間。留意紫圈。PS:圖中的Data Segment泛指BSS, Data, Heap。有些文檔有說明:數據段有三個子區域,分別是BSS, Data, Heap。

缺頁異常(Fault Page)

? ?每次調用malloc(),系統都只是給進程分配線性地址(VM),并沒有隨即分配頁框(RAM)。系統盡量將分配頁框的工作推遲到最后一刻—用到時缺頁異常處理。這種頁框按需延遲分配策略最大好處之一:充分有效地善用系統稀缺資源RAM。

? ?當指針引用的內存頁沒有駐留在RAM中,即在RAM找不到與之對應的頁框,則會發生缺頁異常(對進程來說是透明的),內核便陷入缺頁異常處理。發生缺頁異常有幾種情況:1.只分配了線性地址,并沒有分配頁框,常發生在第一次訪問某內存頁。2.已經分配了頁框,但頁框被回收,換出至磁盤(交換區)。3.引用的內存頁,在進程空間之外,不屬于該進程,可能已被free()。我們使用一段偽代碼來大致了解缺頁異常。

      /* @filename: example_2_5.c */

…

demo()

{

    char *p;

    //分配了100Kbytes線性地址

    if ((p = malloc(1024*100)) != NULL)  // L0

    {

        *p = ‘t’;     // L1

	… //過去了很長一段時間,不管系統忙否,長久不用的頁框都有可能被回收

	*p = ‘m’;	   // L2

	p[4096] = ‘p’;   // L3

	…

	free(p);  //L4

	if (p == NULL)

	{

		*p = ‘l’; // L5

	}

    }

}

…
    
    • L0,函數malloc()通過brk()給進程分配了100Kbytes的線性地址區域(VM).然而,系統并沒有隨即分配頁框(RAM)。即此時,進程沒有占用100Kbytes的物理內存。這也表明了,你時常在使用top的時候VIRT值增大,而RES值卻不變的原因。

    • L1,通過*p引用了100Kbytes的第一頁(4Kbytes)。因為是第一次引用此頁,在RAM中找不到與之相對應的頁框。發生缺頁異常(對于進程而言缺頁異常是透明的),系統靈敏地捕獲這一異常,進入缺頁異常處理階段:接下來,系統會分配一個頁框(RAM)映射給它。我們把這種情況(被訪問的頁還沒有被放在任何一個頁框中,內核分配一新的頁框并適當初始化來滿足調用請求),也稱為Demand Paging。

    • L2,過了很長一段時間,通過*p再次引用100Kbytes的第一頁。若系統在RAM找不到它映射的頁框(可能交換至磁盤了)。發生缺頁異常,并被系統捕獲進入缺頁異常處理。接下來,系統則會分配一頁頁框(RAM),找到備份在磁盤的那“頁”,并將它換入內存(其實因為換入操作比較昂貴,所以不總是只換入一頁,而是預換入多頁。這也表明某些文檔說:”vmstat某時出現不少si并不能意味著物理內存不足”)。凡是類似這種會迫使進程去睡眠(很可能是由于當前磁盤數據填充至頁框(RAM)所花的時間),阻塞當前進程的缺頁異常處理稱為主缺頁(major falut),也稱為大缺頁(參見下圖)。相反,不會阻塞進程的缺頁,稱為次缺頁(minor fault),也稱為小缺面。

    • L3,引用了100Kbytes的第二頁。參見第一次訪問100Kbytes第一頁, Demand Paging。

    • L4,釋放了內存:線性地址區域被刪除,頁框也被釋放。

    • L5,再次通過*p引用內存頁,已被free()了(用戶進程本身并不知道)。發生缺頁異常,缺面異常處理程序會檢查出這個缺頁不在進程內存空間之內。對待這種編程錯誤引起的缺頁異常,系統會殺掉這個進程,并且報告著名的段錯誤(Segmentation fault)。

? ?

? ?主缺頁異常處理過程示意圖,參見 Page Fault Handling

頁框回收PFRA

? ?隨著網絡并發用戶數量增多,進程數量越來越多(比如一般守護進程會fork()子進程來處理用戶請求),缺頁異常也就更頻繁,需要緩存更多的磁盤數據(參考下篇OS Page Cache),RAM也就越來越緊少。為了保證有夠用的頁框供給缺頁異常處理,Linux有一套自己的做法,稱為PFRA。PFRA總會從用戶態進內存程空間和頁面緩存中,“竊取”頁框滿足供給。所謂”竊取”,指的是:將用戶進程內存空間對應占用的頁框中的數據swap out至磁盤(稱為交換區),或者將OS頁面緩存中的內存頁(還有用戶進程mmap()的內存頁)flush(同步fsync())至磁盤設備。PS:如果你觀察到因為RAM不足導致系統病態式般慢,通常都是因為缺頁異常處理,以及PFRA在”盜頁”。我們從以下幾個方面了解PFRA。

候選頁框:找出哪些頁框是可以被回收?

  • 進程內存空間占用的頁框,比如數據段中的頁(Heap, Data),還有在Heap與Stack之間的匿名映射頁(比如由malloc()分配的大內存)。但不包括Stack段中的頁。

  • 進程空間mmap()的內存頁,有映射文件,非匿名映射。

  • 緩存在頁面緩存中Buffer/Cache占用的頁框。也稱OS Page Cache。

頁框回收策略:確定了要回收的頁框,就要進一步確定先回收哪些候選頁框

  • 盡量先回收頁面緩存中的Buffer/Cache。其次再回收內存空間占用的頁框。

  • 進程空間占用的頁框,要是沒有被鎖定,都可以回收。所以,當某進程睡眠久了,占用的頁框會逐漸地交換出去至交換區。

  • 使收LRU置換算法,將那些久而未用的頁框優先被回收。這種被放在LRU的unused鏈表的頁,常被認為接下來也不太可能會被引用。

  • 相對回收Buffer/Cache而言,回收進程內存頁,昂貴很多。所以,Linux默認只有swap_tendency(交換傾向值)值不小于100時,才會選擇換出進程占用的RES。其實交換傾向值描述的是:系統越忙,且RES都被進程占用了,Buffer/Cache只占了一點點的時候,才開始回收進程占用頁框。PS:這正表明了,某些DBA提議將MySQL InnoDB服務器vm.swappiness值設置為0,以此讓InnoDB Buffer Pool數據在RES呆得更久。

  • 如果實在是沒有頁框可回收,PFRA使出最狠一招,殺掉一個用戶態進程,并釋放這些被占的頁框。當然,這個被殺的進程不是胡亂選的,至少應該是占用較多頁框,運行優選級低,且不是root用戶的進程。

激活回收頁框:什么時候會回收頁框?

    • 緊急回收。系統內核發現沒有夠用的頁框分配,供給讀文件和內存缺頁處理的時候,系統內核開始”緊急回收頁框”。喚醒pdflush內核線程,先將1024頁臟頁從頁面緩存寫回磁盤。然后開始回收32頁框,若反復回收13次,還收不齊32頁框,則發狠殺一個進程。

    • 周期性回收。在緊急回收之前,PFRA還會喚醒內核線程kswapd。為了避免更多的“緊急回收”,當發現空閑頁框數量低于設置的警告值時,內核線程kswapd就會被喚醒,回收頁框。直到空閑的頁框的數量達到設定的安全值。PS:當RES資源緊張的時候,你可以通過ps命令看到更多的kswapd線程被喚醒。

    • OOM。在高峰時期,RES高度緊張的時候,kswapd持續回收的頁框供不應求,直到進入”緊急回收”,直到 OOM。

Paging 和Swapping

? ?這二個關鍵字在很多地方出現,譯過來應該是Paging(調頁),Swapping(交換)。PS:英語里面用得多的動詞加上ing,就成了名詞,比如building。咬文嚼字,實在是太難。看二圖

? ?

? ?Swapping的大部分時間花在數據傳輸上,交換的數據也越多,意味時間開銷也隨之增加。對于進程而言,這個過程是透明的。由于RAM資源不足,PFRA會將部分匿名頁框的數據寫入到交換區(swap area),備份之,這個動作稱為so(swap out)。等到發生內存缺頁異常的時候,缺頁異常處理程序會將交換區(磁盤)的頁面又讀回物理內存,這個動作稱為si(swap in)。每次Swapping,都有可能不只是一頁數據,不管是si,還是so。Swapping意味著磁盤操作,更新頁表等操作,這些操作開銷都不小,會阻塞用戶態進程。所以,持續飚高的si/so意味著物理內存資源是性能瓶頸。

? ?

? ?Paging,前文我們有說過Demand Paging。通過線性地址找到物理地址,找到頁框。這個過程,可以認為是Paging,對于進程來講,也是透明的。Paging意味著產生缺頁異常,也有可能是大缺頁,也就意味著浪費更多的CPU時間片資源。

總結

? ?1.用戶進程內存空間分為5段,Text, DATA, BSS, Heap, Stack。其中Text只讀可執行,DATA全局變量和靜態變量,Heap用完就盡早free(),Stack里面的數據是臨時的,退出函數就沒了。

? ?2.glibc malloc()動態分配內存。使用brk()或者mmap(),128Kbytes是一個臨界值。避免內存泄露,避免野指針。

? ?3.內核會盡量延后Demand Paging。主缺頁是昂貴的。

? ?4.先回收Buffer/Cache占用的頁框,然后程序占用的頁框,使用LRU置換算法。調小vm.swappiness值可以減少Swapping,減少大缺頁。

? ?5.更少的Paging和Swapping

? ?6.fork()繼承父進程的地址空間,不過是只讀,使用cow技術,fork()函數特殊在于它返回二次。

:Linux內存點滴 用戶進程內存空間


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 中文字幕第66页永久乱码 | 黄页网址大全免费观看美女 | 国产羞羞视频在线播放 | 13一14周岁毛片免费 | 狠狠久久 | 免费又爽又黄禁片视频在线播放 | 国产成人精品精品欧美 | 中文字幕在线不卡精品视频99 | 精品一级毛片 | 一区二区视频在线观看 | 91亚洲国产成人久久精品网址 | 2019天天干天天操 | 国产成人综合一区人人 | 一区二区三区四区视频在线 | 亚洲福利 影院 | 狠狠色婷婷丁香综合久久韩国 | 久久中文字幕亚洲精品最新 | 日日草夜夜草 | 爱爱视频欧美 | 国产免费播放 | 手机看片欧美 | 日韩一级片在线观看 | 青草青草久热精品视频在线观看 | 国产日韩欧美在线观看不卡 | 大学生不戴套毛片视频 | 欧美日韩国产成人综合在线 | 91国内视频在线观看 | 久热精品视频在线观看 | 日韩在线观看网站 | 国产女人又爽又大 | 精品福利一区 | 一级毛片在线观看视频 | 97精品视频在线观看 | 亚洲精品久久久中文字幕 | 久久精品中文字幕不卡一二区 | 这里只有精品视频在线观看 | 91精品国产爱久久久久 | 狠狠操天天操 | 国产成人欧美一区二区三区的 | 99精品视频在线观看免费播放 | 国产精品成人免费 |