Linux用戶空間內存管理

虛擬內存

每一個進程都有本身獨立的地址空間,進程的數據存儲在這個地址空間中,使得進程之間相互不影響。以32位x86的Linux系統爲例,其地址空間範圍是0x00000000-0xFFFFFFFF,也就是4G。這4G並非真實的4G物理內存,設想一下,若是每一個進程都佔用4G的物理內存,那即便再大的內存條也抗住不這樣的消耗。所以,現代操做系統都採用了虛擬內存的設計。虛擬內存和物理內存都按照必定的大小分紅不少頁,好比每頁是4k的話,4G的虛擬內存就能夠分紅1048576頁。虛擬內存的頁經過一張映射關係表與實際物理內存的頁創建對應關係,以下圖所示。算法

圖1

4G的虛擬地址空間並不是都被使用,不多有程序會用滿虛擬地址空間,對於使用了的虛擬地址也並非所有都映射到物理內存。實際上只有一部分映射到物理內存,一部分映射到磁盤的交換區(swap分區),這樣就大大減小了單個程序對物理內存的佔用。數組

進程訪問虛擬地址空間的某個地址的過程是這樣的:進程訪問某個地址,首先根據這個地址計算獲得分頁,而後查詢映射關係表,若是該分頁映射到了物理內存幀上,則從物理內存中的數據取出返回;若是該分頁沒有映射到物理內存幀上,則將該分頁的數據從磁盤加載到內存中,並更新映射關係表。之因此能夠採用這種方式來設計有兩個前提緣由:bash

(1)剛被訪問過的數據,頗有可能被再次訪問;函數

(2)二八原則——80%的時間都在訪問20%的數據;測試

Linux內存的分佈

Linux的內存管理將這4GB的地址分爲兩個部分,一部分是內核空間的內存,一部分是用戶空間的內存,內核空間佔據3G-4G範圍的地址,用戶空間佔據0G-3G範圍的地址。內核空間是操做系統內核使用內存,用戶空間就是運行在操做系統上的程序可使用的內存。ui

Linux內核空間

Linux內核空間是Linux內核使用的地址空間,Linux內核老是駐留在內存中,用戶級進程不能訪問內核的地址空間。以前所提到的虛擬內存的頁表與物理內存的映射關係表就保存在內核的地址空間裏。內核空間的內存分配很是複雜,本文主要討論用戶空間的內存分配。spa

Linux用戶空間

Linux用戶地址空間從低位到高位的順序能夠分爲:文本段(Text Segment)、初始化數據段(Data Segment)、未初始化數據段(Bss Segment)、堆(Heap)、棧(Stack)和環境變量區(Environment variables)操作系統

文本段命令行

用戶空間的最低位是文本段,包含了程序運行的機器碼。文本段具備只讀屬性,防止進程意外修改了指令致使程序出錯。並且對於多進程的程序,能夠共享同一份程序代碼,這樣減小了對物理內存的佔用。設計

可是文本段並非從0x0000 0000開始的,而是從0x0804 8000開始。0x0804 8000如下的地址是保留區,進程是不能去訪問該地址段的數據,所以C語言中將爲空的指針指向0。

初始化數據段

文本段上面就是初始化的數據段,數據段包含顯示初始化的全局變量和靜態變量。當程序被加載到內存中時,從可執行文件中讀取這些數據的值,並加載到內存。所以,可執行文件中須要保存這些變量的值。

Bss

Bss段包含未初始化的全局變量和靜態變量,還包含顯示初始化爲0的全局變量(根據編譯器的實現)。當程序被加載到內存中時,這一段內存就會被初始化爲0。可執行文件中只須要保存這一段內存的起始地址就行,所以減少了可執行文件的大小。

堆從下自上增加(根據實現),用於動態分配內存。堆的頂端成爲program break,能夠經過brk和sbrk函數調整堆頂的位置。c語言經過malloc函數實現動態內存分配,經過free釋放分配的內存,後面會詳細描述這兩個函數的實現。堆上的內存經過一個雙向鏈表進行維護,鏈表的每一個節點保存這塊內存的大小是否可用等信息。在堆上分配內存可能會致使如下問題: (1)分配的內存,沒有釋放,就會致使內存泄漏; (2)頻繁的分配小塊的內存有可能致使堆上都是剩餘的小塊的內存,這稱爲內存碎片;

棧是一個動態增加和收縮的段,棧是自頂向下增加。棧由棧幀組成,每調用一個函數,系統會爲每一個當前調用的函數分配一個棧幀,棧幀從存儲了參數的實參,以及函數中使用的局部變量,當函數返回時,該函數的棧幀就會彈出,函數中的局部變量所以也就被銷燬了。

環境變量

在棧上面還有一小段空間,這段空間裏保存的是環境變量和命令行參數,環境變量和命令行參數都是指向字符串的數組argv和environ。

malloc和free實現

動態內存的分配是經過維護一個雙向鏈表來實現,每一個節點保存該內存塊的大小的使用狀況。malloc的分配有多種算法,好比首次適配原則,最優適配原則等。咱們這裏採用首次適配原則。實際上free函數,當堆頂有大塊的內存時,會經過sbrk函數下降堆頂的地址,咱們這裏並不作處理。

malloc和free函數

/* my_malloc.h */
#ifndef _MY_MALLOC_H_
#define _MY_MALLOC_H_

#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>

//保存每一個內存塊的信息
typedef struct _MEM_CONTROL_BLOCK_ {
    unsigned int uiBlockSize;      //當前塊的大小
    unsigned int uiPrevBlockSize;  //前一個內存塊的大小
    bool bIsAvalible;              //該內存塊是否已經被分配內存
} MemControlBlock;

#define INIT_BLOCK_SIZE (0x21000) //初始化堆的大小
#define MEM_CONTROL_BLOCK_SIZE (sizeof(MemControlBlock))

void* g_pMallocStartAddr;     //維護堆底地址
void* g_pMallocEndAddr;       //維護堆頂地址

//初始化堆段
void malloc_init() {
    g_pMallocStartAddr = sbrk(INIT_BLOCK_SIZE);
    g_pMallocEndAddr = g_pMallocStartAddr + INIT_BLOCK_SIZE;

    //初始化時堆只有一個內存塊
    MemControlBlock* pFirstBlock;
    pFirstBlock = (MemControlBlock*)g_pMallocStartAddr;
    pFirstBlock->bIsAvalible = 1;
    pFirstBlock->uiBlockSize = INIT_BLOCK_SIZE;
    pFirstBlock->uiPrevBlockSize = 0;
}

void* my_malloc(unsigned int uiMallocSize) {
    static bool bIsInit = false;
    if(!bIsInit)
    {
        malloc_init();
        bIsInit = true;
    }

    void* pCurAddr = g_pMallocStartAddr;
    MemControlBlock* pCurBlock = NULL;
    MemControlBlock* pLeaveBlock = NULL;
    void* pRetAddr = NULL;

    //判斷是否到了堆頂
    while (pCurAddr < g_pMallocEndAddr)
    {
        pCurBlock = (MemControlBlock*)pCurAddr;
        if (pCurBlock->bIsAvalible)
        {
            //判斷該塊可用的內存大小是否知足分配的需求
            if (pCurBlock->uiBlockSize - MEM_CONTROL_BLOCK_SIZE >= uiMallocSize)
            {
                //該塊分配空間後剩餘的空間是否足夠分配一個控制塊,若是不能則把該塊所有分配了
                if ((pCurBlock->uiBlockSize - MEM_CONTROL_BLOCK_SIZE) <= (uiMallocSize + MEM_CONTROL_BLOCK_SIZE))
                {
                    pCurBlock->bIsAvalible = 0;
                    pRetAddr = pCurAddr;
                    break;
                }
                else
                {
                    //分配內存,並將剩餘的空間獨立成一個塊
                    pLeaveBlock = (MemControlBlock*)(pCurAddr + MEM_CONTROL_BLOCK_SIZE + uiMallocSize);
                    pLeaveBlock->bIsAvalible = 1;
                    pLeaveBlock->uiBlockSize = pCurBlock->uiBlockSize - MEM_CONTROL_BLOCK_SIZE - uiMallocSize;
                    pLeaveBlock->uiPrevBlockSize = MEM_CONTROL_BLOCK_SIZE + uiMallocSize;

                    pCurBlock->bIsAvalible = 0;
                    pCurBlock->uiBlockSize = MEM_CONTROL_BLOCK_SIZE + uiMallocSize;

                    pRetAddr = pCurAddr;
                    break;
                }
            }
            else
            {
                pCurAddr += pCurBlock->uiBlockSize;
                continue;
            }
        }
        else
        {
            pCurAddr += pCurBlock->uiBlockSize;
            continue;
        }
    }

    //已有的塊中找不到合適的塊,則經過sbrk函數增長堆頂地址
    if (!pRetAddr)
    {
        unsigned int uiAppendMemSize = uiMallocSize + MEM_CONTROL_BLOCK_SIZE;
        unsigned int uiPrevBlockSize = pCurBlock->uiBlockSize;
        if(*((int*)sbrk(uiAppendMemSize)) == -1)
        {
            return NULL;
        }
        g_pMallocEndAddr = g_pMallocEndAddr + uiAppendMemSize;
        pCurBlock = (MemControlBlock*)pCurAddr;
        pCurBlock->bIsAvalible = 0;
        pCurBlock->uiBlockSize = uiAppendMemSize;
        pCurBlock->uiPrevBlockSize = uiPrevBlockSize;

        pRetAddr = pCurAddr;
    }

    return pRetAddr + MEM_CONTROL_BLOCK_SIZE;
}

void my_free(void* pFreeAddr) {
    if (pFreeAddr == NULL)
    {
        return;
    }

    MemControlBlock* pCurBlock = (MemControlBlock*)(pFreeAddr - MEM_CONTROL_BLOCK_SIZE);
    MemControlBlock* pPrevBlock = (MemControlBlock*)(pFreeAddr - MEM_CONTROL_BLOCK_SIZE - pCurBlock->uiPrevBlockSize);
    MemControlBlock* pNextBlock = (MemControlBlock*)(pFreeAddr - MEM_CONTROL_BLOCK_SIZE + pCurBlock->uiBlockSize);
    if (pCurBlock->bIsAvalible == 0)
    {
        pCurBlock->bIsAvalible = 1;

        //判斷前一個內存塊是否可用
        if (pCurBlock->uiPrevBlockSize != 0 && pPrevBlock->bIsAvalible)
        {
            pPrevBlock->uiBlockSize += pCurBlock->uiBlockSize;

            if((void*)pNextBlock < g_pMallocEndAddr)
            {
                pNextBlock->uiPrevBlockSize = pPrevBlock->uiBlockSize;
            }
        }
    }

    return;
}

#endif //_MY_MALLOC_H_
複製代碼

測試程序

這個測試程序就是循環在堆上動態分配內存,而後釋放內存,能夠選擇釋放起始塊的位置,也能夠選擇間隔的塊數量。

/*test_malloc.c*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "my_malloc.h"

#define MAX_ALLOCS 1000

int main(int argc, char const *argv[])
{
    if(argc < 3 || strcmp(argv[1], "--help") == 0)
    {
        printf("Usage: %s num_allocs block_size [step [min [max]]]\n", argv[1]);
        return -1;
    }

    int iNumAllocs = atoi(argv[1]);
    int iBlockSize = atoi(argv[2]);
    if(iBlockSize > MAX_ALLOCS)
    {
        printf("Out range of the max mallocs.\n");
    }

    int iStep, iMin, iMax;
    iStep = (argc > 3) ? atoi(argv[3]) : 1;
    iMin = (argc > 4) ? atoi(argv[4]) : 1;
    iMax = (argc > 5) ? atoi(argv[5]) : iNumAllocs;

    printf("Initial program break: %10p\n", sbrk(0));

    void* pArr[iNumAllocs];
    memset(pArr, 0, sizeof(pArr));
    for(int i = 0; i < iNumAllocs; ++i)
    {
        pArr[i] = my_malloc(iBlockSize);
        if(pArr[i] == NULL)
        {
            printf("malloc failed.\n");
            return -1;
        }
        printf("After malloc, program break: %10p\n", sbrk(0));
    }

    printf("After alloc, program break: %10p\n", sbrk(0));

    for (int i = iMin; i <= iMax; i += iStep)
    {
        my_free(pArr[i - 1]);
    }

    printf("After free, program break: %10p\n", sbrk(0));

    return 0;
}
複製代碼

測試結果

$:./a.out 10 100 2 
Initial program break:   0xa13000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After malloc, program break:   0xa34000
After alloc, program break:   0xa34000
After free, program break:   0xa34000複製代碼
相關文章
相關標籤/搜索