首頁 > 軟體

Linux記憶體管理詳述

2020-06-16 17:31:06

MMU

MMU=Segmentation Unit+Paging Unit //MMU: Memory Management Unit

logical address=>Segmentation Unit=>linear address=>Paging Unit=>physical address

  • Linux系統中採用虛擬記憶體管理技術來進行記憶體空間的管理, 即: 每個進程都可以擁有0~4G-1的虛擬記憶體地址空間(虛擬的,並不是真實存在的), 其中0~3G-1之間的地址空間叫做使用者空間,而3G~4G-1之間的地址空間叫做核心空間,由作業系統(Paging Unit)負責建立虛擬記憶體地址到真實實體記憶體/檔案的對映, 因此不同進程中的虛擬記憶體地址看起來是一樣的, 但所對應的實體記憶體/檔案是不一樣的,%p列印出來的是虛擬記憶體地址(線性地址), 不是真實的地址
  • 一般來說,絕大多數程式都執行在使用者空間中, 不能直接存取核心空間, 但是核心提供了一些相關的函數可以用於存取核心空間,
  • 虛擬記憶體技術可以解決實體記憶體不夠用的問題eg:我們需要4G實體記憶體=>1G 的真實實體記憶體,3G的硬碟
  • 記憶體地址的基本單位是位元組, 記憶體對映的基本單位是記憶體頁, 目前主流的OS一個記憶體頁的大小是4Kb;
  • Segmentation Fault:
    試圖操作沒有操作許可權的記憶體區域時可能引發段錯誤, eg: 試圖修改唯讀常數區中的資料內容時
    試圖存取沒有經過對映的虛擬地址時可能引發段錯誤, eg: 讀取頂定義地址(無效地址)中的資料內容時

malloc()

#include <stdlib.h>
void *malloc(size_t size);
  • 使用malloc()申請動態記憶體時, 除了申請引數指定大小的記憶體空間之外, 還會申請額外的12byte(也可能不是12)用於儲存該動態記憶體的管理資訊, eg:大小, 是否空閒etc.
  • 使用malloc()申請動態記憶體時, 注意避免對記憶體空間的越界存取, 以避免破壞該動態記憶體的管理資訊, 也就避免Segmentation fault的產生
  • 一般來說, 使用malloc()函數申請比較小塊的動態記憶體時, 作業系統會一次性對映33個記憶體頁的儲存空間, 以防止多次malloc, 提高系統效率
  • malloc()在linux平台的實現會呼叫sbrk()

eg

 #include<stdlib.h>
int *p1=(int*)malloc(sizeof(int));  //會一口氣分配33頁, 把p1的管理資訊放在p1之後的12byte
int *p2=(int*)malloc(sizeof(int));  //因為p1時分配的33頁還沒用完, 所以直接分配在p1的管理資訊後
//管理資訊的區域可以寫入,但是你寫了之後free(p1)就會段錯誤,所以不要寫
//超出33page的記憶體你存取都不行, 直接段錯誤
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(){
    printf("當前進程進程號:%dn",getpid());
    int *pi=(int*)malloc(sizeof(int));
    printf("pi=%pn",pi);   //0x21000 就是33個記憶體頁//0x 1000 就是 1個記憶體頁
    
    //故意越界一下試試, 不超過33記憶體頁的範圍
    *(pi+1024*30)=250;
    printf("*(pi+1024*30)=%dn",*(pi+1024*30));
    //沒有發生段錯誤
    
    //故意越界一下試試, 超過33記憶體頁的範圍
    *(pi+1024*33)=250;  //ATTENTION:pi是int*, 所以pi+1024*33可是pi+4*1024*33byte啊
    printf("*(pi+1024*33)=%dn",*(pi+1024*33));
    //發生段錯誤
    while(1);
    return 0;
}
/*
$ ./a.out 
當前進程進程號:2787
pi=0x9c40008
*(pi+1024*30)=250
Segmentation fault (core dumped)
*/

free()

a) #include <stdlib.h>
void free(void *ptr);

frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc() or realloc()

Note:

  • 使用free釋放多少, 則從對映的總量中減去多少,當所有的動態記憶體全部釋放時, 系統可能會保留33個記憶體頁, 以提高效率
    free() does not check the pointer passed to see whether it is NULL and does not set the pointer to NULL before it returns, while setting a pointer to NULL after freeing is a good practice.If the pointer is NULL, then no action is performed and the program will execute without terminating abnormally;
void safefree(void **pp){
    if(pp!=NULL&&*pp!=NULL){
    free(*pp);
    *pp=NULL;
    }
}
#define safeFree(p) safeFree((void**)&(p))
int main(){
    int *pi;
    pi=(int*)malloc(sizeof(int));
    *pi=5;
    printf(“Before:%pn”,pi);
    safefree(pi);
    printf(“After:%pn”,pi);
    safefree(pi);
    return 0;
}

getpagesize()

#include <unistd.h>
int getpagesize(void);

returns the number of bytes in a memory page, where "page" is a fixed-length block, the unit for memory allocation and file mapping performed by mmap(2).

sbrk()

 #include <unistd.h>
void *sbrk(intptr_t increment); //intptr_t 是int的別名, _t都可以認為是int的別名,偶爾是short 或long etc

調整動態記憶體/虛擬記憶體的大小, increments the program's data space by increment bytes. Calling sbrk() with an increment of 0 can be used to find the current location of the program break.(當前動態分配記憶體的末尾位置)(程式斷點,program break,可以理解為offset的位置),成功返回the previous program break,失敗返回(void*)-1
increment>0表示申請動態記憶體, 就是記憶體空間變大

increment=0表示獲取當前動態記憶體的末尾地址, 就是記憶體空間不變

increment<0表示釋放動態記憶體, 就是記憶體空間變小

#include<stdlib.h>
#include<unistd.h>
//使用sbrk()獲取一個有效地址
void* pv=sbrk(0);
if((void*)-1==pv)
    perror("sbrk"),exit(-1);
//使用sbrk()在當前位置基礎上申請一個int長度的記憶體
void* p2=sbrk(sizeof(int));
if((void*)-1==p2)
    perror("sbrk"),exit(-1);

Note:雖然sbrk()既能申請動態記憶體, 也能釋放動態記憶體, 但是使用sbrk函數申請動態記憶體時更加方便,??般來說, 使用sbrk函數申請比較小塊的動態記憶體時, 作業系統會對映1個記憶體頁大小的記憶體空間, 當申請的動態記憶體超過1個記憶體也時, 系統會再次對映1個記憶體頁的大小, 當所有動態記憶體釋放完畢時, 系統不會保留任何的動態記憶體對映, 與malloc()相比, 比較節省記憶體空間, 也不會申請額外的儲存空間, 但是頻繁分配時效率沒有malloc()高

brk()

#include <unistd.h>
int brk(void *addr);

調整動態記憶體/虛擬記憶體的大小, sets the end of the data segment to the value specified by addr,成功返回0,失敗返回-1, 設errno為ENOMEM

  • 如果addr>原來的末尾地址,申請動態記憶體, 記憶體空間變大
  • 如果addr=原來的末尾地址,記憶體空間不變
  • 如果addr<原來的末尾地址,釋放動態記憶體, 記憶體空間變小
//使用brk()釋放動態記憶體  
#include<stdlib.h>   
#include<unistd.h>   
int res=brk(pv);
if(-1==res)
    perror("brk"),exit(-1);

Note:雖然brk()既能申請動態記憶體, 又能釋放動態記憶體, 但是釋放動態記憶體更加方便, 而sbrk()申請動態記憶體更加方便, 因此一般情況下兩個函數搭配使用, sbrk()專門用於申請, brk()專門用於釋放

Memory Leak

A memory leak occurs when allocated memory is never used again but is not freed !!!A problem with memory leaks is that the memory cannot be reclaimed and used later. The amount of memory available to the heap manager is decreased. If memory is repeatedly allocated and then lost, then the program may terminate when more memory is needed but malloc() cannot allocate it because it ran out of memory. In extreme cases, the operationg system may crash

  1. losing the address:

    int *pi=(int*)malloc(sizeof(int));
    *pi=5;  //之前申請的記憶體已經無法釋放了,因為address已經丟了
    …
    pi=(int*)malloc(sizeof(int);
  2. Hidden memory leaks,Memory leaks can also occur when the program should release memory but does not. A hidden memory leak occurs when an object is kept in the heap even thouth the object is no longrer needed. This is frequently the result of programmer oversight. The primary problem with this type of leak is that the obkect is using memory that is no longer nedded and should be returned to the heap. In the worst case, the heap manager may not be able to allocate memory when requested, possibly forcing the program to terminate. At best, we are holding unneeded memory.
  3. Structure deallocation without free pointers defined in it. When memory is allocated for a strcture, the rentime system will not aytomaticallu allocate memory for any pointers defined within it. Likewise, when the structure goes away, the runtime system will not automatically deallocate memory asigned to the structure’s pointers
    The correct way to allocate and deallocate a structure pointer with pointer fields:

    void initializePerson(Person *person, sonst char* fn,const char * ln, const chat* title,uint age){ 
    person->firstName=(char*)malloc(strlen(fn)+1); 
        strcpy(person->firstName,fn); 
        ... 
        person->age=age; 
    } 
    void deallocatePerson(Person* person){ 
        free(person->firstName); 
        free(person->lastName); 
        free(person->title); 
    } 
    void processPerson(){ 
        Person* pPerson; 
        pPerson=(Person*)malloc(sizeof(Person)); 
        initilizePerson(pPterson,"Peter","Underwood","Manager",36); 
        ... 
        deallocatePerson(pPerson); 
        free(pPerson); 
    }

Dangling Pointers

a pointer still references the original memory after it has been freed. The use of dangling pointer can result in:

  1. Unpredicatable behavior if the memory is accessed
  2. Segmentation fauts if the memory is no longer accessible
  3. Potential security risks

Several approaches for pointer-induced problem:

  1. Setting a pointer to NULL after freeing it.
  2. Writing special functions to replace the free function.
  3. Some systems(rumtime/debugger) will overwrite data when it is freed
  4. Use third-party tools to detect dangling pointers and other problem
  5. Displaying pointer values can be helpful in debugging dangling pointer

Note:When a pointer is passed to a function, it is always good practice to verify it is not NULL before using it;

2 ways returning a pointer referencing heap

  1. Allocate memory within the function using malloc and return its address. The caller is responsible for deallocating the memory returned;
  2. Pass an object to the function where it is modified.This makes the allocation and deallocation of the object's memory the caller's responsibility.

Several potential problems can occur when returning a pointer from a function:

  • Return an uninitialized pointer
  • Returning a pointer to an invalid address
  • Returning a pointer to a local variable
  • Returning a pointer but failing to free it =>memory leak

A pointer to one function can be cast to another type.

This should be done carefully since the runtime system doesn’t verify that parameters used by a function pointer are correct.It is also possible to cast a function pointer to a different function pointer and then back. The resulting pointer will be equal to the original pointer, The size of function pointers used are not necessarily the same.
Note that conversion between function pointers and pointers to data is not guaranted to work;

Always make sure you use the correct argument list for function pointers, Failure to do so will result in indeterminate behavior

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-10/135727.htm


IT145.com E-mail:sddin#qq.com