歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux內存管理詳述

Linux內存管理詳述

日期:2017/2/28 13:48:21   编辑:Linux教程

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("當前進程進程號:%d\n",getpid());
    int *pi=(int*)malloc(sizeof(int));
    printf("pi=%p\n",pi);   //0x21000 就是33個內存頁//0x 1000 就是 1個內存頁
    
    //故意越界一下試試, 不超過33內存頁的范圍
    *(pi+1024*30)=250;
    printf("*(pi+1024*30)=%d\n",*(pi+1024*30));
    //沒有發生段錯誤
    
    //故意越界一下試試, 超過33內存頁的范圍
    *(pi+1024*33)=250;  //ATTENTION:pi是int*, 所以pi+1024*33可是pi+4*1024*33byte啊
    printf("*(pi+1024*33)=%d\n",*(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:%p\n”,pi);
    safefree(pi);
    printf(“After:%p\n”,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

Copyright © Linux教程網 All Rights Reserved