MMU MMU=Segmentation Unit+Paging Unit //MMU: Memory Management Unit logical address = Segmentation Unit= linear address = Paging Unit= physical addres ...
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
losing the address:
int *pi=(int*)malloc(sizeof(int)); *pi=5; //之前申請的記憶體已經無法釋放了,因為address已經丟了 … pi=(int*)malloc(sizeof(int);
- 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.
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:
- Unpredicatable behavior if the memory is accessed
- Segmentation fauts if the memory is no longer accessible
- Potential security risks
Several approaches for pointer-induced problem:
- Setting a pointer to NULL after freeing it.
- Writing special functions to replace the free function.
- Some systems(rumtime/debugger) will overwrite data when it is freed
- Use third-party tools to detect dangling pointers and other problem
- 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
- Allocate memory within the function using malloc and return its address. The caller is responsible for deallocating the memory returned;
- 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