Minix內存管理(1)

 <?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" />CSDN_Dev_Image_2003-9-142225080.png

 

本文首先介紹內存管理最基本的部分:物理內存的分配和回收,然後介紹和內存分配相關的系統調用,例如FORKEXECBRK、信號處理等,其中會涉及到進程管理和文件系統。通過本文的介紹,大家會對Minix的內存管理有大致的瞭解,並且能夠清楚的看到一段可執行代碼如何被裝入內存,分配資源後執行的。

 

注:下文中程序的行號和《操作系統:設計與實現》一書中附錄A保持一致

 

2物理內存的分配和回收

內存管理的策略有很多,比如交換、分頁、分段、段頁式等,Minix的內存管理非常簡單:既不分頁也不交換。存儲管理器保存着一張按照內存地址排列的空洞列表。當由於執行系統調用FORKEXEC需要內存時,系統特用首次適配算法對空洞列表進行按索找出一個足夠大的空洞。一旦一個程序被裝入內存,它將一直保持在原來的位置直到運行結束,它永遠不會被換出或移動到內存的其他位置去,爲它分配的空間也不會增長或縮小。

物理內存管理主要包括下面幾種功能:

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;

                  /*1889118895行:把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_initsendrec(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就可以讀取。

後面我們將會看到更多的利用消息和內核打交道的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章