本週學習的內容是虛擬內存html
要理解虛擬存儲器,須要理解如下幾個問題:linux
1.簡化共享:利用虛擬地址來映射物理地址,使得可讓多個進程的不一樣虛擬地址映射同一塊物理地址,好比相似於printf,這一類經常使用的庫,不會把printf的代碼拷貝到每個進程,而是讓不一樣進程都使用同一塊printf.。git
返回目錄程序員
要理解地址翻譯,須要理解如下幾個問題:算法
下面的轉化圖將說明虛擬地址到物理地址的一個過程
編程
返回目錄api
要理解存儲器映射,須要理解如下幾個問題:數組
mmap()函數
,用來映射物理內存。在驅動程序中,應用程序以設備文件爲對象,調用mmap()函數
,內核進行內存映射的準備工做,生成vm_area_struct結構體
,而後調用設備驅動程序中定義的mmap函數
。mmap()函數
:
void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *start, size_t length);
#include <sys/mman.h>
EACCES:訪問出錯 EAGAIN:文件已被鎖定,或者太多的內存已被鎖定 EBADF:fd不是有效的文件描述詞 EINVAL:一個或者多個參數無效 ENFILE:已達到系統對打開文件的限制 ENODEV:指定文件所在的文件系統不支持內存映射 ENOMEM:內存不足,或者進程已超出最大內存映射數量 EPERM:權能不足,操做不容許 ETXTBSY:已寫的方式打開文件,同時指定MAP_DENYWRITE標誌 SIGSEGV:試着向只讀區寫入 SIGBUS:試着訪問不屬於進程的內存區
prot:指望的內存保護標誌,不能與文件的打開模式衝突。是如下的某個值,能夠經過or運算合理地組合在一塊兒
PROT_EXEC //頁內容能夠被執行
PROT_READ //頁內容能夠被讀取
PROT_WRITE //頁能夠被寫入
PROT_NONE //頁不可訪問緩存
flags:指定映射對象的類型,映射選項和映射頁是否能夠共享。它的值能夠是一個或者多個如下位的組合體
MAP_FIXED //使用指定的映射起始地址,若是由start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。若是指定的起始地址不可用,操做將會失敗。而且起始地址必須落在頁的邊界上。
MAP_SHARED //與其它全部映射這個對象的進程共享映射空間。對共享區的寫入,至關於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。
MAP_PRIVATE //創建一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。這個標誌和以上標誌是互斥的,只能使用其中一個。
MAP_DENYWRITE //這個標誌被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要爲這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會獲得保證。當交換空間不被保留,同時內存不足,對映射區的修改會引發段違例信號。
MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出內存。
MAP_GROWSDOWN //用於堆棧,告訴內核VM系統,映射區能夠向下擴展。
MAP_ANONYMOUS //匿名映射,映射區不與任何文件關聯。
MAP_ANON //MAP_ANONYMOUS的別稱,再也不被使用。
MAP_FILE //兼容標誌,被忽略。
MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標誌只在x86-64平臺上獲得支持。
MAP_POPULATE //爲文件映射經過預讀的方式準備好頁表。隨後對映射區的訪問不會被頁違例阻塞。
MAP_NONBLOCK //僅和MAP_POPULATE一塊兒使用時纔有意義。不執行預讀,只爲已存在於內存中的頁面創建頁表入口。安全
fd:有效的文件描述詞。若是MAP_ANONYMOUS被設定,爲了兼容問題,其值應爲-1。
offset:被映射對象內容的起點。
咱們須要瞭解的是存儲器以及其在內存中實現動態分配的方法
快頁模式DRAM(FPM DRAM):許對同一行連續的訪問能夠直接從行緩衝區獲得服務。
擴展數據輸出DRAM(EDO DRAM):容許單獨的CAS信號在時間上靠的更緊密一點。
同步DRAM(SDRAM):用驅動存儲控制器相同的外部時鐘信號的上升沿來替代許多的異步信號,比異步的更快。
雙倍數據速率同步DRAM(DDR DRAM):經過使用兩個時鐘沿做爲控制信號,使得DRAM的速度翻倍。
視頻RAM(VRAM):用在圖形系統的緩衝中。輸出經過以此對內部緩衝區的整個內容進行移位獲得的;容許對內存並行地讀和寫。
垃圾收集須要完成的三件事情
在網上我沒有找到C語言的垃圾收集知識,我找到了Java的,這裏我就以Java內存垃圾收集機制來談談垃圾收集的概念
垃圾收集器在對堆進行回收以前須要判斷這些對象中哪些還「存活着」,哪些已經「死去」。
/*mem_leak*/ void mem_leak(int n) { int *gb_n = (int *)malloc(sizeof(int) * n); //gb_n 在這裏沒有被釋放,也沒有被引用 return ; }
這段代碼分配了一個內存快,在釋放以前程序就返回了,若是這是一個服務器或守護進程的程序就很糟糕了,它會形成內存空間被佔滿的狀況,通常現象是程序運行變慢最終終止。
void scanf() { scanf("%d",$val); } 然而有些程序員特別是初學者,很容易出現如下錯誤: /*scanf_bug*/ void scanf_bug() { scanf("%d",val); }
傳遞的是val的內容而不是他的地址,在這種狀況下,scanf把變量的內容解釋爲一個地址,試圖將一個字寫到這個位置,最想的狀況是程序當即終止,而在糟糕的狀況下阿拉的內容被寫到對應的內存中某個合法的讀寫區域,因而就會覆蓋原來地址中的值,一般這樣會形成災難性的或使人困惑的後果.
/*uninit*/ int uninit_bug(int **array, int *p, int num) { int i = 0; int j = 0; int *temp_p = (int *)malloc(sizeof(int) * num); for(i = 0; i < num; i++) { //temp_p[i] = NULL; for(j = 0; j > num; j++) { temp_p[i] = array[i][j] * p[j]; } return temp_p; } }
上面的這短代碼中顯示的是:
不正確地認爲指針temp_p被初始化爲零,正確的應該在程序中把
temp_p[i]設置爲零: temp_p[i] = NULL;
或者使用另外一個方法calloc來動態分配內存:
int *temp_p = (int *)calloc(1, sizeof(int) * num);
方法calloc來動態分配內存時自動的將指針temp_p被初始化爲零.
/*pioter_to_obj_size*/ int **poiter_to_obj_size(int num1, int num2) { int i = 0; int **p_array = (int **)malloc(sizeof(int) * num1); // *p_array = NULL; for(i = 0; i < num1; i++) { p_array[i] = (int *)malloc(sizeof(int) * num2); } return p_array; }
如今咱們來分析上面這段代碼,這段代碼的目的是建立一個由num1個指針組成的數組,每一個指針都指向一個包含num2個int類型的數組.
在程序中咱們把:
int **p_array = (int **)malloc(sizeof(int*) * num1);
寫成了:
int **p_array = (int **)malloc(sizeof(int) * num1);
這時程序實際上建立的是一個int類型的數組,這段代碼會在int和指向int的指針大小相同的機器上運行良好,若是把這段代碼放在int和指向int的指針大小不一樣的機器上運行就會出現一些使人困惑的奇怪的錯誤.
/*off_by_one*/ int **off_by_one(int num 1, int num2) { int i = 0; int **p_array = (int **)malloc(sizeof(int*) * num1); *p_array = NULL; //for(i = 0; i < num1; i++) for(i = 0; i <= num1; i++) { p_array[i] = (int *)malloc(sizeof(int) * num2); } return p_array; }
這段代碼建立了一個num1個元素的指針數組,可是後面的代碼卻試圖初始化數組的num1+1個元素,這樣最後一個就會覆蓋數組的後面的某個地址中的數據.
/*use_another_obj*/ int *use_another_obj(int **binheap, int *size) { int *pac = binheap[0]; binheap[0] = binheap[*size - 1]; *size--; heapify(binheap, *size, 0); return pac; }
本意是減小size指針指向的整數的值,卻由於運算符的優先級出現了減小指針本身的值!正確的寫法應該是這樣的:
binheap[0] = binheap[*size - 1]; (*size)--;
/*wrong_poiter_op*/ int *wrong_poiter_op(int *poiter, int num) { while(*poiter && * != num) { poiter += sizeof(int); } return poiter; }
想用這個程序遍歷一個int類型的數組,返回一個指針指向num的首次出現,可是結果卻不是我指望的那樣,由於每次循環時poiter += sizeof(int);都把指針加上了4,這樣代碼就遍歷了數組中的每4個整數了,正確的應該是這樣的:
poiter++ += sizeof(int);
/*bufoverflow*/ void bufoverflow_bug() { char buf[256]; gets(buf); }
上面的這段程序代碼就出現了緩衝區錯誤,由於gets函數拷貝一個任意長度的串到緩衝區了,因此咱們必須使用fgets函數:
fgets(buf);
由於這個函數限制了輸入串的大小.
/*ref_freeed_heap_data*/ int *ref_freeed_heap_data(int n, int m) { int i = 0; int *x = NULL; int *y = NULL; x = (int *)malloc(sizeof(int) * n); free(x); y = (int *)malloc(sizeof(int) * m); for(i = 0; i < m; i++) { y[i] = x[i]++; //x freeed!!!!!! } return y; }
程序引用了已經釋放的數據:free(x);
/*ref_no_val*/ int *ref_no_val() { int num; return # }
這裏他已經再也不指向一個合法的變量了.
返回目錄
解答:
以Java爲例,內存運行時區域的各個部分,其中程序計數器、虛擬機棧、本地方法棧3個區域會隨着線程而生,隨線程而滅;棧中的棧幀隨着方法的進行有條不紊地執行着出棧和入棧操做。每個棧幀中分配多少內存基本上是在類結構肯定下來時就已知得,所以這幾個區域的內存分配回收都具有肯定性,在這幾個區域就不須要過多的考慮回收的問題,由於在方法結束或線程結束時內存就被回收了。
而Java堆和方法區則不同,一個接口中的實現類須要的內存可能不同,一個方法中的多個分支須要的內存也可能不同,咱們只有在程序處於運行期間才能知道會建立那些對象,這部份內存的分配和回收都是動態的,垃圾收集器所關注的是這部份內存。
返回目錄
無
返回目錄
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 | |
第一週 | 0/0 | 1/1 | 10/10 | |
第三週 | 200/200 | 2/3 | 10/20 | |
第四周 | 100/300 | 1/4 | 10/30 | |
第五週 | 200/500 | 3/7 | 10/40 | |
第六週 | 500/1000 | 2/9 | 30/70 | |
第七週 | 500/1500 | 2/11 | 15/85 | |
第八週 | 223/1723 | 3/14 | 15/100 | |
第九周 | 783/2506 | 3/17 | 15/115 | |
第九周 | 0/2506 | 3/20 | 12/127 | |
第十週 | 620/3126 | 2/22 | 20/147 | |
第十一週 | 390/3516 | 2/24 | 17/164 |
計劃學習時間:17小時
實際學習時間:17小時