Unix/Linux內存管理

jopen 10年前發布 | 21K 次閱讀 Linux UNIX

一、底層結構

        采用三層結構,實際使用中可以方便映射到兩層或者三層結構,以適用不同的硬件結構。最下層的申請內存函數get_free_page。之上有三種類型的內存分配函數:

        1.kmalloc類型。內核進程使用,基于切片(slab)技術,用于管理小于內存頁的內存申請。思想出發點和應用層的內存緩沖池同出一轍。但它針對內核結構,特別處理應用場景固定,不考慮釋放。

        2.vmalloc類型。內核進程使用。用于申請不連續內存。

        3.brk/mmap類型。用戶進程使用。malloc/free實現的基礎。

二、內存管理的相關函數圖

        STL  ->  內存自動分配和自動回收(C++)

           |

        C++  ->  new分配內存,delete回收內存

           |

          C  ->  malloc分配內存,free回收內存

           |

        Unix 系統函數 ->  sbrk/brk  分配和回收內存

           |

        Unix底層系統函數  ->  mmap/munmap分配回收

                                                                                        (用戶層)

----------------------------------------------------------------------------

                                                                                        (內核層)

        Unix內核函數  kmalloc/vmalloc/get_free_page

三、進程與內存

        a.所有進程(執行的程序)都必須占用一定數量的內存

        b.對任何一個普通進程來講,它都會涉及到5種不同的數據段,其內存空間劃分為:

                1.代碼區    ——    存放代碼/函數,也就是說它是可執行程序的內存中的鏡像。(只讀)

                2.全局區    ——    保存全局變量,static局部變量。

                3.BSS段     ——    未初始化的全局變量,BSS段在main函數執行之前會自動清零

                4.棧區       ——    局部變量,包括函數的形參,棧區內存自動分配和自動回收。

                5.堆區       ——    程序員自己管理的區域,malloc/free操作的都是堆區。

                6.只讀常量區    ——    存放字符串常量和const修飾的全局變量

                注:只讀常量區和代碼區非常接近,有些書把只讀常量區和代碼區合并為代碼區。

        c.進程如何組織這些區域?

                從小到大次序:代碼區、只讀常量區、全局區、BSS段、堆區、棧區

                堆區在離前面四個區不遠的地址空間開始,從小到大分配,棧區從3G開始,從大到小分配。主要為了避免堆區和棧區重疊。

        d.查看內存分配

                Linux把一切都看做成文件,內存也可以在文件中查看。每個進程都在/proc目錄下有一個對應的子目錄,以進程ID作為子目錄名。進程ID是系統對進程的標識。可以用ps-aux命令查看進程。

                cat /proc/進程ID/maps    可以查看當前進程的內存情況。

四、虛擬內存管理技術

        Linux使用了虛擬內存地址。每個Linux中的進程都有 0~4G的虛擬內存地址,就是0~4G的數字。虛擬內存地址在開始時只是一個數字,不對應任何的內存。虛擬內存地址必須先映射一段物理內存或硬盤上的文件才能被使用。所謂的分配內存其實就是讓虛擬內存地址映射一段物理內存。如果使用沒有映射的虛擬內存地址就會引發段錯誤。

        程序員所操作的內存地址都是虛擬內存地址,看不到物理內存地址。

        0~4G的虛擬內存地址中,0~3G是用戶使用,叫做“用戶空間”,3G~4G是內核使用的,叫做“內核空間”。用戶空間不能直接使用內核空間,但可以通過內核空間提供的一些函數(系統調用)訪問內核空間。

        注:內存管理的基本單位是4096 byte (4K),叫內存頁。內存的映射和回收都是以內存頁作為基本單位。

五、進程內存管理

        進程內存管理的對象是進程線性地址空間上的內存鏡像,這些內存鏡像其實就是進程使用的虛擬內存區域(memory region)。進程虛擬空間是個32或64位的“平坦”(獨立的連續區間)地址空間(空間的具體大小取決于體系結構)。要統一管理這么大的平坦空間可絕非易事,為了方便管理,虛擬空間被劃分為許多大小可變的(但必須是4096byte的整數倍數)內存區域,這些區域在進程線性地址中像停車位一樣有序排列。這些區域的劃分原則是“將訪問屬性一致的地址空間存放在一起”,所謂訪問屬性一致無非是指“可讀、可寫、可執行等”。

六、物理內存管理(頁管理)

        Linux內核管理物理內存是通過分頁機制實現的,它將整個內存劃分成無數4K(在i386體系結構中)大小頁,從而分配和回收內存的基本單位便是內存頁了。利用分頁管利用助于靈活分配內存地址,因為分配時不必要求必須有大塊的連續內存,系統可以東一頁、西一頁的湊出所需要的內存供進程使用。雖然如此,但是實際上系統使用內存還是傾向于分配連續的內存塊,因為分配連續內存時,頁表不需要修改,因此能降低刷新率(頻繁刷新會很大增加訪問速度)。

七、brk/sbrk的虛擬內存管理

        void *sbrk(int size);

            size = 0    返回sbrk/brk上次的末尾地址,代表取當前的位置,

            size > 0    分配內存空間,返回上次的末尾地址,代表分配size字節的內存,

            size < 0    釋放空間,代表回收size字節內存。

        int brk(void* ptr);

            直接修改訪問的有效范圍的末尾地址,釋放空間形成一個完整的page,則該頁映射被解除

            返回:0    分配成功

                      -1    分配失敗

        經驗:sbrk在分配內存上簡單,brk在釋放內存上簡單。因此,開發大多數使用sbrk分配內存,使用brk釋放內存。

八、系統底層的內存映射(mmap/munmap)

1 #include <sys/mman.h>
2 void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
3 int munmap(void *start, size_t length);

        參數公共部分:

            start:指向欲映射的內存起始地址,通常設為 NULL,代表讓系統自動選定地址,映射成功后返回該地址。

            length:代表將文件中多大的部分映射到內存。    映射空間大小。建議4k倍數,不是4K倍數,自動對齊。

        mmap獨有部分:

            prot:映射區域的保護方式。可以為以下幾種方式的組合:

                1.PROT_EXEC 映射區域可被執行

                2.PROT_READ 映射區域可被讀取    

                3.PROT_WRITE 映射區域可被寫入

                4.PROT_NONE 映射區域不能存取

            flags:影響映射區域的各種特性。在調用mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。

                1.MAP_FIXED 如果參數start所指的地址無法成功建立映射時,則放棄映射,不對地址做修正。通常不鼓勵用此標志。

                2.MAP_SHARED對映射區域的寫入數據會復制回文件內,而且允許其他映射該文件的進程共享。

                3.MAP_PRIVATE 對映射區域的寫入操作會產生一個映射文件的復制,即私人的“寫入時復制”(copy on write)對此區域作的任何修改都不會寫回原來的文件內容。

                4.MAP_ANONYMOUS建立匿名映射。此時會忽略參數fd,不涉及文件,而且映射區域無法和其他進程共享。

                    5.MAP_DENYWRITE只允許對映射區域的寫入操作,其他對文件直接寫入的操作將會被拒絕。

                    6.MAP_LOCKED 將映射區域鎖定住,這表示該區域不會被置換(swap)。

            fd:要映射到內存中的文件描述符。如果使用匿名內存映射時,即flags中設置了MAP_ANONYMOUS,fd設為-1。有些系統不支持匿名內存映射,則可以使用fopen打開/dev/zero文件,然后對該文件進行映射,可以同樣達到匿名內存映射的效果。

            offset:文件映射的偏移量,通常設置為0,代表從文件最前方開始對應,offset必須是分頁大小的整數倍。

        返回值:

            若映射成功則返回映射區的內存起始地址,否則返回MAP_FAILED(-1),錯誤原因存于errno 中。

九、errno錯誤代碼

           1.EBADF 參數fd 不是有效的文件描述詞

            2.EACCES 存取權限有誤。如果是MAP_PRIVATE 情況下文件必須可讀,使用MAP_SHARED則要有PROT_WRITE以及該文件要能寫入。

            3.EINVAL 參數start、length 或offset有一個不合法。

            4.EAGAIN 文件被鎖住,或是有太多內存被鎖住。

            5.ENOMEM 內存不足。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!