一個虛擬儲存系統要求硬體和核心軟體之間緊密協作,版本與版本之間細節都不盡相同,在這裡我們的目標是對Linux的虛擬系統做一個描述,大致理解作業系統是如何組織虛擬儲存空間以及如何缺頁的。
虛擬位址空間:一套虛擬位址的集合。cpu從一個有N=2^n個地址的虛擬位址空間中產生虛擬位址來訪問記憶體。Linux為每一個進程維持了一個單獨的虛擬位址空間,虛擬位址空間可以有間隙。
(一)Linux虛擬儲存空間定義:Linux的虛擬儲存空間是由一些地區(也叫做段)組成的集合。一個地區(area)就是已經存在著的(已指派的)虛擬儲存空間的連續片(chunk),這些頁是以某種方式關聯的。例如,程式碼片段、資料區段、堆、共用庫段、以及使用者棧都是不同的地區。每個存在的虛擬頁面都儲存在某個地區中,而不屬於某個地區的虛擬頁面是不存在的,並且不能被進程引用。核心不用記錄那些不存在的虛擬頁面,這樣的頁也不用占儲存空間、磁碟、或者核心本身的用存任何額外資源。
b.進程中虛擬儲存空間的資料結構:核心為系統中每個進程維護一個單獨的任務結構。任務結構中的元素包含或指向核心運行該進程所需要的所有資訊。(可理解為結構體裡面有好多個結構體總共標明了所有資訊)(所需要的資訊舉例:PID、指向使用者棧的指標、可執行目標檔案的名字以及程式計數器)。
(二)Linux儲存空間映射:虛擬儲存空間地區 與一個 磁碟上的對象 關聯起來的過程稱為儲存空間映射。儲存空間映射的目的是初始化該虛擬儲存空間地區的內容。虛擬儲存空間地區可以映射到Unix檔案系統中的普通檔案和匿名檔案這兩種類型的對象中的一種。
Unix進程可以使用mmap函數來建立新的虛擬儲存空間地區,並將對象映射到這些地區中。例如:
int main(int argc, char **argv){ int fd; //擷取檔案描述符 char *fp; //接受虛擬儲存空間返回的指標 if( (fd=open(argv[1], O_RDWR))==-1 ){ printf("open error\n"); exit(-1); } //用mmap函數建立虛擬位址空間 fp=mmap(NULL, 1000000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); printf("%s" ,fp); //用munmap函數刪除虛擬位址空間 printf("==%d\n", munmap(fp, 10) ); printf("%s\n" ,fp);return 0;}
一個對象可以被映射到虛擬儲存空間的一個地區,要麼作為共用對象,要麼作為私人對象。一個映射到共用對象的虛擬儲存空間地區叫做共用地區,類似地,也有私人地區。
1.共用對象:類似於c語言中函數對全域變數和私人變數的操作,進程對共用地區的任何寫操作,對於其他進程的 映射於該共用對象的共用地區來說,是可見的。而且,這些變化也會反應在磁碟上的原始對象中。在這裡,即使對象被映射到了多個共用地區,實體儲存體器中也只需要存放共用對象的一個拷貝。
2.私人對象:與共用對象一樣,實體儲存體器只儲存私人對象的一份拷貝。兩個進程可以共用同一個物理拷貝,將私人對象映射到它們自己的私人地區。
私人對象是使用一種叫做寫時拷貝的巧妙技術被映射到虛擬儲存空間中的。對於每個映射私人對象的進程,相應私人地區的頁表條目都被標記為唯讀,並且地區結構被標記為私人的寫時拷貝。當有進程試圖寫私人地區的某個頁面時,這個寫操作就會觸發一個保護屏障。此時,故障處理常式會在實體儲存體器上重新建立這個頁面的一個拷貝,給這個新拷貝可寫入權限,更新頁表條目指向這個新拷貝。當故障處理常式返回時,CPU重新執行這個寫操作,現在就可以寫了。
也就是說,不管是私人對象還是共用對象,在實體儲存體器上都只有一份拷貝,都可以共用。但是,進程可以順利地在共用地區執行寫操作 並且為其他的 把這個共用對象映射到虛擬儲存空間的進程可見 還會反映到磁碟上的原始對象中。而進程對私人地區的寫操作依賴於寫時拷貝技術,不但對其他進程不可見,而且不會反映在磁碟上的對象中。
(三)動態儲存裝置器分配:因為經常要到程式實際運行時,我們才知道某些資料結構的大小,所以用動態儲存裝置分配器更方便,而且有更好的可移植性。
1.動態儲存裝置分配器維護著一個進程的虛擬儲存空間地區,稱為堆。分配器將堆視為一組不同大小的塊兒的集合來維護。每個塊就是一個連續的虛擬儲存空間片(chunk),已指派的塊顯示地保留為供應用程式使用,空閑塊可用來分配。
2.顯示分配器:顯示分配器要求顯示地釋放任何已指派的塊。例如,c標準庫提供的malloc程式包的顯示分配器。c程式通過調用malloc函數來調用一個塊,並調用free函數來釋放一個塊。c++裡面的new和delete操作符與c中的malloc和free相當。
3.隱式分配器:隱式分配器也叫做垃圾收集器,要求分配器檢測一個已指派塊何時不再被程式所使用,那麼就釋放這個塊。自動釋放未使用的已指派的塊的過程叫做垃圾收集,例如java之類的進階語言就依賴垃圾收集來釋放已指派的塊。
4.實現一個簡單的顯示分配器:構造一個分配器是一件富有挑戰性的任務。設計空間很大,有多種塊格式、空閑鏈表格式,以及放置、分割和合并策略可供選擇。
下面,我們選擇特定格式來實現一個簡單的分配器。
代碼如下:
#include<stdio.h>#include<netinet/in.h>#include<arpa/inet.h>#include<unistd.h>#include<stdlib.h>#include<assert.h>#include<sys/socket.h>#include<string.h>#include<errno.h>#define WSIZE 4 //字的大小#define DSIZE 8 //雙字的大小#define CHUNKSIZE (1<<12) //擴充堆時的預設大小#define MAX(x, y) ((x) > (y) ? (x) :(y))//將大小和已指派位結合起來並返回一個值,可以把它存放在頭部或腳部#define PACK(size, alloc) ((size) | (alloc))//read and write a word at address p#define GET(p) (*(unsigned int*)(p))#define PUT(p,val) (*(unsigned int*)(p)=(val))//read the size and allocated files from address p#define GET_SIZE(p) (GET(p) & ~0x7) //從地址p處的頭部或尾部返回大小#define GET_ALLOC(p) (GET(p) & 0x1) //從地址p處的頭部或尾部返回已指派位//Give block ptr bp,compute address of its header and footer#define HDRP(bp) ((char *)(bp)-WSIZE) //返回指向這個塊兒頭部指標#define FTRP(bp) ((char *)(bp)+GET_SIZE(HDRP(bp))-DSIZE) //尾部//Give block ptr bp, compute address of next and previous blocks////根據這一塊兒的頭部確定這塊兒的大小從而跳到下一塊兒#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE( ((char *)(bp) -WSIZE ) )) //返回指向後面塊的指標//根據上一塊兒的尾部以獲得上一塊兒的大小從而跳到上一塊兒#define PREV_BLKP(bp) ((char *)(bp) + GET_SIZE( ((char *)(bp)-DSIZE) )) //返回指向前面塊的指標#define MAX_HEAP (10*(1<<12)) //10MB/*Private global variables*/static char *mem_heap; //Points to first byte of heapstatic char *mem_brk; //Points to last byte of heap plus 1static char *mem_max_addr; //Max legal heap addr plus 1static char *heap_listp; //指向序言塊兒static void *coalesce(void *bp); //合并空閑塊兒/* *函式宣告部分*/void mem_init(void); //申請一大塊兒地址空間void *mem_sbrk(int incr); //請求額外的堆儲存空間,拒絕縮堆int mm_init(void); //初始化堆Dstatic voia *extend_heap(size_t words); static void *extend_heap(size_t words); //擴充堆void mm_free(void *bp); //堆釋放static void *find_fit(size_t asizei); //首次適配搜尋static void place(void *bp, size_t asize); //將asize大小的空閑位 置為 已指派位//mem_init -Initial the memory system modelvoid mem_init(void){ mem_heap=(char *)malloc(MAX_HEAP); mem_brk=(char *)mem_heap; mem_max_addr=(char *)(mem_heap + MAX_HEAP);}/*mem_sbrk _Simple model of the sbrkfunction.Extends the heap by incr * bytes and returns the start address of the new area.In this model, * the heap cannot be shrunk*/void *mem_sbrk(int incr){ char *old_brk=mem_brk; if((incr < 0) || ((mem_brk + incr) > mem_max_addr)){ errno=ENOMEM; fprintf(stderr, "ERROR: mem_sbrk faild. Ran out of memory...\n"); return (void *)-1; } mem_brk += incr; return (void *)old_brk;}//合并空閑塊兒static void *coalesce(void *bp){ size_t prev_alloc=GET_ALLOC(FTRP(PREV_BLKP(bp))); //擷取前面塊兒的已指派位 size_t next_alloc=GET_ALLOC(HDRP(NEXT_BLKP(bp))); //擷取後面塊兒的已指派位 size_t size=GET_SIZE(HDRP(bp)); if(prev_alloc && next_alloc){ return bp; //前後都被佔用 case1 } else if(prev_alloc && !next_alloc){ size += GET_SIZE(HDRP(NEXT_BLKP(bp))); PUT(HDRP(bp), PACK(size, 0)); PUT(FTRP(bp), PACK(size, 0)); } //前一個塊兒空閑 else if(!prev_alloc && next_alloc){ size += GET_SIZE(HDRP(PREV_BLKP(bp))); PUT(FTRP(bp), PACK(size, 0)); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); bp = PREV_BLKP(bp); } //後一個塊兒空閑 else{ size += GET_SIZE(HDRP(PREV_BLKP(bp)))+GET_SIZE(FTRP(NEXT_BLKP(bp))); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0)); bp = PREV_BLKP(bp); } //前後塊兒都空閑 return bp;}//擴充堆函數static void *extend_heap(size_t words){ char *bp; size_t size; size = (words % 2) ? (words+1)*WSIZE : words * WSIZE; if((long)(bp=mem_sbrk(size)) == -1) return NULL; //初始化閒置頭尾和結尾塊兒 PUT(HDRP(bp), PACK(size, 0)); PUT(FTRP(bp), PACK(size, 0)); PUT(HDRP(NEXT_BLKP(bp)), PACK(0,1) ); //合并空閑塊兒 return coalesce(bp);}// 建立一個帶初始空閑塊兒的堆int mm_init(void){ /*create the initial empty heap*/ if( ( heap_listp = mem_sbrk(4*WSIZE)) == (void *)-1 ) return -1; PUT(heap_listp, 0); //填充塊兒 PUT(heap_listp + (1*WSIZE), PACK(DSIZE, 1)); //序言塊兒 PUT(heap_listp + (2*WSIZE), PACK(DSIZE, 1)); //序言塊兒 PUT(heap_listp + (3*WSIZE), PACK(0,1)); //結尾塊兒 heap_listp += 2*WSIZE; /*Extend the empty heap eith a free block of CHUNKSIZE bytes*/ if(extend_heap(CHUNKSIZE/WSIZE) == NULL) return -1; return 0;}//釋放空間void mm_free(void *bp){ size_t size=GET_SIZE(HDRP(bp)); PUT(HDRP(bp), PACK(size, 0)); PUT(FTRP(bp), PACK(size, 0)); coalesce(bp);}/*static void *find_fit(size_t asize){ char *heap_cir=heap_listp; //指向合適塊兒的頭 while(heap_cir < mem_brk){ if( (GET_ALLOC(heap_cir) ==0) && (GET_SIZE(heap_cir) >= asize) ){ return heap_cir; } else heap_cir += GET_SIZE(heap_cir); } return NULL;}*///對隱式空閑鏈表進行首次適配搜尋static void *find_fit(size_t asize){ /*First fit search*/ void *bp; for(bp = heap_listp; GET_SIZE(HDRP(bp)) > 0 ; bp=NEXT_BLKP(bp) ){ if(!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp))) ){ return bp; } } return NULL;}//將空閑位標誌為分配位static void place(void *bp, size_t asize){ size_t csize=GET_SIZE(HDRP(bp)); //如果一個塊兒不夠 if((csize-asize) >= (2*DSIZE)){ PUT(HDRP(bp), PACK(asize, 1)); PUT(FTRP(bp), PACK(asize, 1)); bp=NEXT_BLKP(bp); //佔用下一個塊兒一些空間 PUT( HDRP(bp), PACK(csize-asize, 0) ); PUT( FTRP(bp), PACK(csize-asize, 0) ); }else{ PUT(HDRP(bp), PACK(csize, 1)); PUT(FTRP(bp), PACK(csize, 1)); }}//從空閑鏈表分配一個塊兒void *mm_malloc(size_t size){ size_t asize; size_t extendsize; char *bp; /*Ignore spurious requests*/ if(size == 0){ return NULL; } /*Adjust block size to include overhead and alignment reqs*/ if(size <= DSIZE){// printf("size <= DSIZE\n"); asize = 2*DSIZE; } else asize = DSIZE*( (size + (DSIZE) + (DSIZE-1) )/DSIZE ); //Search the free list for a fit if((bp=find_fit(asize)) != NULL){ place(bp, asize);// printf("!=NULL 242\n"); return bp; } extendsize=MAX(asize, CHUNKSIZE); if( (bp=extend_heap(extendsize/WSIZE)) == NULL){ return NULL; } place(bp, asize); return bp;}int main(void){ size_t a=4; int *bp; int *m; int *n; mem_init();//先用malloc函數 申請一大塊兒空間,也可使用mmap函數 mm_init(); //初始化堆 printf("\nthe heap = %p\n", mem_heap); //輸出malloc申請的空間起始地址 /*列印最佳化了的分配器使用的單獨的私人全域變數它指向序言塊兒的下一塊*/ printf("listp=%p\n\n", heap_listp); bp=(int *)mm_malloc(a); //調用mm_malloc函數申請一塊空間 printf("bp=%p\n\n",bp); //列印出地址 m=(int *)mm_malloc(a); //再申請一塊空間 mm_free(bp); //調用mm_free函數釋放第一次申請的空間 n=(int *)mm_malloc(a); //重新申請一塊空間 printf("n=%p\n",n); //列印出地址 printf("m=%p\n\n",m); //列印出上一次申請的空間的地址 return 0;}
從顯示結果可以看出,mm_malloc函數和mm_free函數分別實現了分配和釋放空間功能。
實現分配器的特定格式分別為:
(a).塊格式為:字是4位元組的對象,雙字是8位元組的對象
(b).資料結構選擇隱式空閑鏈表
(c).放置策略為首次適配
(d).分割策略:將空閑塊分為兩部分。第一部分變成分配塊,剩下的變成一個新的空閑塊。
(e).合并策略為帶邊界標記的合并。
對Linux虛擬儲存空間的介紹大致就說到這裡,剛剛學的新知識理解不深,如果有什麼理解不對的地方,敬請指出。