<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Minix內存管理
1概述
Minix 在設計時被分成了四層,如下圖所示,第1層和第2層是進程管理和I/O任務,合稱爲Minix的核心(kernel), 內存管理(Memory Manager,下文簡稱MM) 並不是內核的一部分,它位於內核之上的第三層,主要處理的是FORK,EXEC,BRK等涉及到內存訪問的系統調用。它和內核之間通過消息來通信。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
本文首先介紹內存管理最基本的部分:物理內存的分配和回收,然後介紹和內存分配相關的系統調用,例如FORK、EXEC、BRK、信號處理等,其中會涉及到進程管理和文件系統。通過本文的介紹,大家會對Minix的內存管理有大致的瞭解,並且能夠清楚的看到一段可執行代碼如何被裝入內存,分配資源後執行的。
注:下文中程序的行號和《操作系統:設計與實現》一書中附錄A保持一致
2物理內存的分配和回收
內存管理的策略有很多,比如交換、分頁、分段、段頁式等,Minix的內存管理非常簡單:既不分頁也不交換。存儲管理器保存着一張按照內存地址排列的空洞列表。當由於執行系統調用FORK或EXEC需要內存時,系統特用首次適配算法對空洞列表進行按索找出一個足夠大的空洞。一旦一個程序被裝入內存,它將一直保持在原來的位置直到運行結束,它永遠不會被換出或移動到內存的其他位置去,爲它分配的空間也不會增長或縮小。
物理內存管理主要包括下面幾種功能:
1. 內存初始化:當MM啓動時需要初始化內存空洞表
2. 分配指定大小的內存
3. 釋放一塊以前分配的內存
4. 返回當前最大空洞的大小
空洞列表的數據結構如下:
18820 #define NR_HOLES 128 /* max # entries in hole table */
18821 #define NIL_HOLE (struct hole *) 0
18822
18823 PRIVATE struct hole {
18824 phys_clicks h_base; /* 空洞的開始地址*/
18825 phys_clicks h_len; /* 空洞的長度 */
18826 struct hole *h_next; /* 指向下一個空洞 */
18827 } hole[NR_HOLES];
18830 PRIVATE struct hole *hole_head; /* pointer to first hole */
18831 PRIVATE struct hole *free_slots; /* ptr to list of unused table slots */
上面的數據結構說明,空洞表中共有128個表項,各個表項之間通過指針連接成一個鏈表。
指針hole_head指向第一個空洞,free_slots的含義在下文會說明
由於MM採用了非常簡單的策略,所以其數據結構也非常簡單,操作無非是鏈表的遍歷,增加,刪除等,學過《數據結構》課程的人應該很容易看懂。
2.1分配內存
18840 PUBLIC phys_clicks alloc_mem(clicks)
18841 phys_clicks clicks; /* 要分配的內存塊的大小 */
18842 {
18843 /* Allocate a block of memory from the free list using first fit. The block
18844 * consists of a sequence of contiguous bytes, whose length in clicks is
18845 * given by 'clicks'. A pointer to the block is returned. The block is
18846 * always on a click boundary. This procedure is called when memory is
18847 * needed for FORK or EXEC.
18848 */
18849
18850 register struct hole *hp, *prev_ptr;
18851 phys_clicks old_base;
18852
18853 hp = hole_head;/*從鏈表頭開始遍歷*/
18854 while (hp != NIL_HOLE) {
18855 if (hp->h_len >= clicks) {
18856 /* We found a hole that is big enough. Use it. */
18857 old_base = hp->h_base; /* 記下老的基地址 */
18858 hp->h_base += clicks; /* 修改空洞的基地址 */
18859 hp->h_len -= clicks; /* 修改空洞的長度*/
18860
18861 /* 如果空洞沒有用完,直接返回,old_base 就是所求*/
18862 if (hp->h_len != 0) return(old_base);
18863
18864 /* 整個空洞都用完了,把該空洞放到一個free list中*/
18865 del_slot(prev_ptr, hp);
18866 return(old_base);
18867 }
18868
18869 prev_ptr = hp;
18870 hp = hp->h_next;
18871 }
18872 return(NO_MEM);
18873 }
代碼比較簡單,從空洞列表的頭開始遍歷,找到一個足夠大小的空洞,修改空洞的基地址和長度,值得注意的是del_slot函數:
18926 PRIVATE void del_slot(prev_ptr, hp)
18927 register struct hole *prev_ptr; /* pointer to hole entry just ahead of 'hp' */
18928 register struct hole *hp; /* pointer to hole entry to be removed */
18929 {
18930 /* Remove an entry from the hole list. This procedure is called when a
18931 * request to allocate memory removes a hole in its entirety, thus reducing
18932 * the numbers of holes in memory, and requiring the elimination of one
18933 * entry in the hole list.
18934 */
18935
18936 if (hp == hole_head)
18937 hole_head = hp->h_next;
18938 else
18939 prev_ptr->h_next = hp->h_next;
18940
18941 hp->h_next = free_slots;
18942 free_slots = hp;
18943 }
這段代碼的含義是把hp所指向的空洞從空洞鏈表中刪除,這是基本的鏈表操作。然後把hp加到free_slots的頭部,這時候大家應該明白free_slots的含義了,它指向一個鏈表的頭部,這個鏈表保存了一系列的空洞,這些空洞的特點是:已經沒有可以分配的空間,或者說其h_len域爲0,實際上是一個空的數據結構。下面我們還會看到free_slots的用法。
2.2釋放內存
18879 PUBLIC void free_mem(base, clicks)
18880 phys_clicks base; /* 要釋放的內存塊的基地址 */
18881 phys_clicks clicks; /* 要釋放的內存塊的長度*/
18882 {
18883 /* Return a block of free memory to the hole list. The parameters tell where
18884 * the block starts in physical memory and how big it is. The block is added
18885 * to the hole list. If it is contiguous with an existing hole on either end,
18886 * it is merged with the hole or holes.
18887 */
18888
18889 register struct hole *hp, *new_ptr, *prev_ptr;
18890
18891 if (clicks == 0) return;
18892 if ( (new_ptr = free_slots) == NIL_HOLE) panic("Hole table full", NO_NUM);
18893 new_ptr->h_base = base;
18894 new_ptr->h_len = clicks;
18895 free_slots = new_ptr->h_next;
/*18891至18895行:把free_slots鏈表上第一個空洞取下來,使其基地址和長度爲要釋放的值,並把free_slots後移到下一個元素*/
18896 hp = hole_head;
18897
18898 /* new_ptr現在指向一個可以重新分配的空洞,下面需要把new_ptr所指的空洞放到空洞列表的合適位置。需要注意的空洞列表是按基地址從小到大排列的。*/
18901
18902 if (hp == NIL_HOLE || base <= hp->h_base) {
18903 /* 直接放到空洞列表的頭部*/
18904 new_ptr->h_next = hp;
18905 hole_head = new_ptr;
18906 merge(new_ptr);
18907 return;
18908 }
18909
18910 /* 需要找到一個合適的位置 */
18911 while (hp != NIL_HOLE && base > hp->h_base) {
18912 prev_ptr = hp;
18913 hp = hp->h_next;
18914 }
18915
18916 /* 在prev_ptr後面插入新的空洞*/
18917 new_ptr->h_next = prev_ptr->h_next;
18918 prev_ptr->h_next = new_ptr;
18919 merge(prev_ptr); /* sequence is 'prev_ptr', 'new_ptr', 'hp' */
18920 }
在釋放內存中用到了merge函數:
18949 PRIVATE void merge(hp)
18950 register struct hole *hp; /* ptr to hole to merge with its successors */
18951 {
/* 從hp指向的空洞開始,向後找兩個空洞,如果這三個空洞是連續的(即一個空洞的基地址加長度等於後面那個空洞的基地址),則把這三個空洞合併*/
18958 register struct hole *next_ptr;
18959
18960 /* If 'hp' points to the last hole, no merging is possible. If it does not,
18961 * try to absorb its successor into it and free the successor's table entry.
18962 */
18963 if ( (next_ptr = hp->h_next) == NIL_HOLE) return;
18964 if (hp->h_base + hp->h_len == next_ptr->h_base) {
18965 hp->h_len += next_ptr->h_len; /* first one gets second one's mem */
18966 del_slot(hp, next_ptr);
18967 } else {
18968 hp = next_ptr;
18969 }
18970
18971 /* If 'hp' now points to the last hole, return; otherwise, try to absorb its
18972 * successor into it.
18973 */
18974 if ( (next_ptr = hp->h_next) == NIL_HOLE) return;
18975 if (hp->h_base + hp->h_len == next_ptr->h_base) {
18976 hp->h_len += next_ptr->h_len;
18977 del_slot(hp, next_ptr);
18978 }
18979 }
2.3獲得最大空洞的大小
18985 PUBLIC phys_clicks max_hole()
18986 {
18987 /* Scan the hole list and return the largest hole. */
18988
18989 register struct hole *hp;
18990 register phys_clicks max;
18991
18992 hp = hole_head;
18993 max = 0;
18994 while (hp != NIL_HOLE) {
18995 if (hp->h_len > max) max = hp->h_len;
18996 hp = hp->h_next;
18997 }
18998 return(max);
18999 }
代碼非常簡單,不再解釋。
2.4空洞初始化
19005 PUBLIC void mem_init(total, free)
19006 phys_clicks *total, *free; /* memory size summaries */
19007 {
19018 register struct hole *hp;
19019 phys_clicks base; /* base address of chunk */
19020 phys_clicks size; /* size of chunk */
19021 message mess;
19022
19023 /* 先形成一個鏈表,讓free_slots指向表頭,hole_head則指向NULL */
19024 for (hp = &hole[0]; hp < &hole[NR_HOLES]; hp++) hp->h_next = hp + 1;
19025 hole[NR_HOLES-1].h_next = NIL_HOLE;
19026 hole_head = NIL_HOLE;
19027 free_slots = &hole[0];
/* 下面的循環不斷的向內核發送消息,獲取物理內存的信息 */
19033 *free = 0;
19034 for (;;) {
19035 mess.m_type = SYS_MEM;
19036 if (sendrec(SYSTASK, &mess) != OK) panic("bad SYS_MEM?", NO_NUM);
19037 base = mess.m1_i1;
19038 size = mess.m1_i2;
19039 if (size == 0) break; /* no more? */
19040
19041 free_mem(base, size); /*注意,這裏的每次釋放最終會形成一個空洞鏈表*/
19042 *total = mess.m1_i3;
19043 *free += size;
19044 }
19045 }
說明:由於MM和內核是分開的,他們之間只能利用消息來通信,mem_init中sendrec(SYSTASK, &mess)含義是向SYSTASK發送一條消息,獲取一塊內存信息,Minix最終會調用下面的函數:
15424 PRIVATE int do_mem(m_ptr)
15425 register message *m_ptr; /* pointer to request message */
15426 {
15427 /* Return the base and size of the next chunk of memory. */
15428
15429 struct memory *memp;
15430
15431 for (memp = mem; memp < &mem[NR_MEMS]; ++memp) {
15432 m_ptr->m1_i1 = memp->base;
15433 m_ptr->m1_i2 = memp->size;
15434 m_ptr->m1_i3 = tot_mem_size;
15435 memp->size = 0;
15436 if (m_ptr->m1_i2 != 0) break; /* found a chunk */
15437 }
15438 return(OK);
15439 }
注意:do_mem函數每次都會找到一塊size不爲0的內存返回,它把基地址寫到樂m1_i1域中,把長度寫到了m1_i2中,把總的內存大小寫到了m1_i3域中,這樣mem_init就可以讀取。
後面我們將會看到更多的利用消息和內核打交道的代碼。