• 大地址空間;
• 進程保護;
• 內存映射;
• 公平的物理內存分配;
• 共享虛擬內存。
實現結構剖析
• 進程保護;
• 內存映射;
• 公平的物理內存分配;
• 共享虛擬內存。
實現結構剖析

(1)內存映射模塊(mmap):負責把磁盤文件的邏輯地址映射到虛擬地址,以及把虛擬地址映射到物理地址
(2)交換模塊(swap)負責控制內存內容的換入與換出,淘汰最近沒訪問的頁,保留最近訪問的頁.
(3)core(核心內存管理模塊):負責內存管理功能.
(4)結構特定模塊:實現虛擬內存的物理基礎
內核空間和用戶空間
Linux簡化了分段機制,使得虛擬地址跟線性地址一樣.Linux將虛擬地址空間(4G)分為最高1G部分內核使用(所有進程共享).最低3G給進程使用

內核占據了虛擬空間的最高1G直接,但映射到地址為0開始,是很簡單的線性映射.PAGE_OFFSET為0xc0000000(物理地址與線性地址之間的位移量)

include/asm/i386/page.h 中對內核空間中地址映射的說明及定義
/*
* This handles the memory map.. We could make this a config
* option, but too many people screw it up, and too few need
* it.
*
* A __PAGE_OFFSET of 0xC0000000 means that the kernel has
* a virtual address space of one gigabyte, which limits the
* amount of physical memory you can use to about 950MB.
*
* If you want more physical memory than this then see the CONFIG_HIGHMEM4G
* and CONFIG_HIGHMEM64G options in the kernel configuration.
*/
#define __PAGE_OFFSET (0xC0000000)
……
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
代碼注釋說明:
如果你的物理內存大於950MB,那么在編譯內核時就需要加
CONFIG_HIGHMEM4G 和 CONFIG_HIGHMEM64G 選項,這種情況我們暫不考慮。如果物理內存小於950MB,則對於內核空間而言,給定一個虛地址 x,其物理地址為“x- PAGE_OFFSET”,給定
一個物理地址 x,其虛地址為“x+ PAGE_OFFSET”。
這里再次說明,宏__pa()僅僅把一個內核空間的虛地址映射到物理地址,而決不適用於
用戶空間,用戶空間的地址映射要復雜得多。
2.內核映像
內核的代碼和數據就叫內核映像.系統啟動時,Linux內核被加載到物理地址為0x001000000開始的地方.1M開始區間.然而
正常運行時,整個內核映像應該在虛擬空間,因此,所有的符號地址都該加一個PAGE_OFFSET.這樣內核映像為0xc01000000處
程的頁目錄 PGD(屬於內核數據結構)就處於內核空間中。在進程切換時,要
將寄存器 CR3 設置成指向新進程的頁目錄 PGD,而該目錄的起始地址在內核空間中是虛地址,
但 CR3 所需要的是物理地址,這時候就要用__pa()進行地址轉換。在 mm_context.h 中就有這么一行語句:
asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));
這是一行嵌入式匯編代碼,其含義是將下一個進程的頁目錄起始地址 next_pgd,通過
__pa()轉換成物理地址,存放在某個寄存器中,然后用 mov 指令將其寫入 CR3 寄存器中。經
過這行語句的處理,CR3 就指向新進程 next 的頁目錄表 PGD 了。
將寄存器 CR3 設置成指向新進程的頁目錄 PGD,而該目錄的起始地址在內核空間中是虛地址,
但 CR3 所需要的是物理地址,這時候就要用__pa()進行地址轉換。在 mm_context.h 中就有這么一行語句:
asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));
這是一行嵌入式匯編代碼,其含義是將下一個進程的頁目錄起始地址 next_pgd,通過
__pa()轉換成物理地址,存放在某個寄存器中,然后用 mov 指令將其寫入 CR3 寄存器中。經
過這行語句的處理,CR3 就指向新進程 next 的頁目錄表 PGD 了。
虛擬內存地址實現機制的關系
• 內存分配和回收機制;
• 地址映射機制;
• 緩存和刷新機制;
• 請頁機制;
• 交換機制;
• 內存共享機制
• 地址映射機制;
• 緩存和刷新機制;
• 請頁機制;
• 交換機制;
• 內存共享機制

內存管理程序通過映射把用戶的邏輯地址映射到物理地址,用戶程序運行時發現虛地址沒對應物理地址就發出(1)請求,如果有空閑的內存
可供分配,就請求分配內存(2),並且把正在使用的物理頁記錄在頁緩存中(3).如果沒有足夠的內存分配,那就調用交換機制,騰出一部分內存(4)(5)
另外在地址映射時要通過TLB來選物理頁(8);交換機制也要用到交換緩存(6).並且把物理頁內容交換到交換文件后也要修改頁表來映射文件地址(7)
--------------Linux內存管理初始化-------------------
Linux啟動時,運行在實模式.隨后要轉到保護模式下運行.
,
1.頁表的初步初始化
/*
* The page tables are initialized to only 8MB here - the final page
* tables are set up later depending on memory size.
*/
.org 0x2000
ENTRY(pg0)//存放的是虛擬地址
.org 0x3000
ENTRY(pg1)
/*
* empty_zero_page must immediately follow the page tables ! ( The
* initialization loop counts until empty_zero_page)
*/
.org 0x4000
ENTRY(empty_zero_page)
/*
* Initialize page tables
*/
movl $pg0-__PAGE_OFFSET,%edi /* initialize page tables 將物理地址存放在edi中,位置為0x1002000處*/
movl $007,%eax /* "007" doesn't mean with right to kill, but
PRESENT+RW+USER */
2: stosl
add $0x1000,%eax
cmp $empty_zero_page-__PAGE_OFFSET,%edi
jne 2b
內核執行此代碼時,頁機制還未啟動,指令還是純物理地址.因為pg0存放的是虛擬地址,因此
$pg0
-
__PAGE_OFFSET獲得其物理地址
pg0存放在相對於內核代碼起點為 0x2000 的地方,即物理地址為 0x00102000,而 pg1 的物理地址
則為 0x00103000。Pg0 和 pg1 這個兩個頁表中的表項則依次被設置為 0x007、0x1007、0x2007
等。其中最低的 3 位均為 1,表示這兩個頁為用戶頁,可寫,且頁的內容在內存中(參見圖
2.24)。所映射的物理頁的基地址則為 0x0、0x1000、0x2000 等,也就是物理內存中的頁面 0、
1、2、3 等等,共映射 2K 個頁面,即 8MB 的存儲空間。由此可以看出,Linux 內核對物理內
存的最低要求為 8MB。緊接着存放的是 empty_zero_page 頁(即零頁),零頁存放的是系統啟
動參數和命令行參數,
則為 0x00103000。Pg0 和 pg1 這個兩個頁表中的表項則依次被設置為 0x007、0x1007、0x2007
等。其中最低的 3 位均為 1,表示這兩個頁為用戶頁,可寫,且頁的內容在內存中(參見圖
2.24)。所映射的物理頁的基地址則為 0x0、0x1000、0x2000 等,也就是物理內存中的頁面 0、
1、2、3 等等,共映射 2K 個頁面,即 8MB 的存儲空間。由此可以看出,Linux 內核對物理內
存的最低要求為 8MB。緊接着存放的是 empty_zero_page 頁(即零頁),零頁存放的是系統啟
動參數和命令行參數,
啟動分頁機制--------------------
代碼功能:將頁目錄swapper_pg_dir的物理地址加入cr3.並且把cr0最高位置為1
/*
* This is initialized to create an identity-mapping at 0-8M ( for bootup
* purposes) and another mapping of the 0-8M area at virtual address
* PAGE_OFFSET.
*/
.org 0x1000
ENTRY(swapper_pg_dir)
.long 0x00102007 //
兩個頁表是用戶頁表、可寫且頁表的內容在內存。.long 0x00103007
.fill BOOT_USER_PGD_PTRS-2,4,0
/* default: 766 entries */
.long 0x00102007
.long 0x00103007
/* default: 254 entries */
.fill BOOT_KERNEL_PGD_PTRS-2,4,0
/*
* Enable paging
*/
3:
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
jmp 1f /* flush the prefetch-queue */
1:
movl $1f,%eax
jmp *%eax /* make sure eip is relocated */


物理內存的描述
Linux把物理內存划分3個層次管理;存儲節點,管理區,和頁表,並且用3個相應的數據結構來描述
頁面數據結構
對一個物理頁面的描述
struct page{
struct list_head list; //通過使用它進入下面的數據結構free_area_struct結構中的雙向鏈隊列
struct address_space * mapping; //用於內存交換的數據結構
unsigned long index;//當頁面進入交換文件后,指向其去向
struct page *next_hash; //自身的指針,這樣就可以鏈接成一個鏈表
atomic t count; //用於頁面交換的計數,若頁面為空閑則為0,分配就賦值1,沒建立或恢復一次映射就加1,斷開映射就減一
unsigned long flags;//反應頁面各種狀態,例如活躍,不活躍臟,不活躍干凈,空閑
struct list_head lru;
unsigned long age; //表示頁面壽命
wait_queue_head_t wait;
struct page ** pprev_hash;
struct buffer_head * buffers;
void * virtual
struct zone_struct * zone; //指向所屬的管理區
}
系統每個物理頁面都有一個Page結構,系統初始化內存的大小建立一個Page的數組mem_map
為了對物理頁面進行有效管理,Linux把物理頁面分為3個區:
• 專供 DMA 使用的 ZONE_DMA 區(小於 16MB);
• 常規的 ZONE_NORMAL 區(大於 16MB 小於 896MB);
• 內核不能直接映射的區 ZONE_HIGME 區(大於 896MB)。
DMA 控制器不能依靠 CPU 內部的 MMU 將連續的虛存頁面映射到物理上也連續的頁面上,因此用於DMA的物理頁面必須加以單獨管理。
• 常規的 ZONE_NORMAL 區(大於 16MB 小於 896MB);
• 內核不能直接映射的區 ZONE_HIGME 區(大於 896MB)。
DMA 控制器不能依靠 CPU 內部的 MMU 將連續的虛存頁面映射到物理上也連續的頁面上,因此用於DMA的物理頁面必須加以單獨管理。
存儲節點的數據結構
typedef struct pglist_data {
zone_t node_zones[MAX_NR_ZONES];//節點的最多3個頁面管理區
zonelist_t node_zonelists[GFP_ZONEMASK+1];//一個管理區指針數組,指向上面的管理區
int nr_zones;
struct page *node_mem_map;//指向具體節點的page結構數組
unsigned long *valid_addr_bitmap;
struct bootmem_data *bdata;
unsigned long node_start_paddr;
unsigned long node_start_mapnr;
unsigned long node_size;
int node_id;
struct pglist_data *node_next;//形成一個單鏈表節點隊列
} pg_data_t;
typedef struct zonelist_struct {
zone_t *zone[MAX_NR_ZONE+1]; //NULL delimited 管理區
Int gfp_mast;
} zonelist_t
zone[]是個指針數組,各個元素按特定的次序指向具體的頁面管理區,表示分配
頁面時先試 zone[0]所指向的管理區, 如果不能滿足要求就試 zone[1]所指向的管理區,
頁面時先試 zone[0]所指向的管理區, 如果不能滿足要求就試 zone[1]所指向的管理區,
內存管理區
typedef struct zone_struct {
/*
* Commonly accessed fields:
*/
spinlock_t lock; 用於暴走對該結構中其他域的串行訪問
unsigned long free_pages;//這個區中現有空閑頁的個數
unsigned long pages_min, pages_low, pages_high;//對這個區最少,次少及最多頁面個數的描述
int need_balance;//與kswapd合在一起
/*
* free areas of different sizes
*/
free_area_t free_area[MAX_ORDER];/在伙伴分配系統中的位圖數組和頁面鏈表
/*
* Discontig memory support fields.
*/
struct pglist_data *zone_pgdat;//本管理區所在的存儲節點
struct page *zone_mem_map;//本管理區的內存映射表
unsigned long zone_start_paddr;//本管理區的物理地址
unsigned long zone_start_mapnr;//mem_map索引
/*
* rarely used fields:
*/
char *name;
unsigned long size;
} zone_t;
free_area[MAX_ORDER]是一組隊列的鏈表每一個隊列保持1 2 4 ...各一個用來分配物理大小快
type struct free_area_struct {
struct list_head free_list
unsigned int *map
} free_area_t
物理頁面的分配和釋放
Linux 把不連續的存儲
空間也歸類為非一致存儲結構(NUMA)。這是因為,不連續的存儲空間本質上是一種廣義的
NUMA,因為那說明在最低物理地址和最高物理地址之間存在着空洞,而有空洞的空間當然是
“不一致”的。所以,在地址不連續的物理空間也要像結構不一樣的物理空間那樣划分出若干
連續且均勻的“節點”。因此,在存儲結構不連續的系統中,每個模塊都有若干個節點,因而
都有個 pg_data_t 數據結構隊列。我們先來看 mm/numa.c 中的 alloc_page()函數:
Linux 把不連續的存儲
空間也歸類為非一致存儲結構(NUMA)。這是因為,不連續的存儲空間本質上是一種廣義的
NUMA,因為那說明在最低物理地址和最高物理地址之間存在着空洞,而有空洞的空間當然是
“不一致”的。所以,在地址不連續的物理空間也要像結構不一樣的物理空間那樣划分出若干
連續且均勻的“節點”。因此,在存儲結構不連續的系統中,每個模塊都有若干個節點,因而
都有個 pg_data_t 數據結構隊列。我們先來看 mm/numa.c 中的 alloc_page()函數:
//表示哪種分配策略,order表示所需物理塊的大小,1,2,4.....
struct page * _alloc_pages(unsigned int gfp_mask, unsigned int order)
{
struct page *ret = 0;
pg_data_t *start, *temp;
#ifndef CONFIG_NUMA
unsigned long flags;
static pg_data_t *next = 0;
#endif
if (order >= MAX_ORDER)
return NULL;
#ifdef CONFIG_NUMA
temp = NODE_DATA(numa_node_id());//通過NUMA_DATA()找到cpu所在節點的數據結構隊列,存放在temp中
#else
spin_lock_irqsave(&node_lock, flags);
if (!next) next = pgdat_list;
temp = next;
next = next->node_next;
spin_unlock_irqrestore(&node_lock, flags);
#endif
start = temp;
while (temp) {
if ((ret = alloc_pages_pgdat(temp, gfp_mask, order)))//從當前節點掃到最后節點,能否滿足分配內存
return(ret);
temp = temp->node_next;
}
temp = pgdat_list;
while (temp != start) {//從頭節點掃到當前節點,視圖分配內存
if ((ret = alloc_pages_pgdat(temp, gfp_mask, order)))
return(ret);
temp = temp->node_next;
}
return(0);
}
一致存儲結構(UMA)中頁面的分配
連續空間 UMA 結構的 alloc_page()是在 include/linux/mm.h 中定義的:
連續空間 UMA 結構的 alloc_page()是在 include/linux/mm.h 中定義的:
#ifndef CONFIG_DISCONTIGMEM
static inline struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)
{
/*
* Gets optimized away by the compiler.
*/
if (order >= MAX_ORDER)
return NULL;
return __alloc_pages(gfp_mask, order,
contig_page_data.node_zonelists+(gfp_mask & GFP_ZONEMASK));
}
#endi
_alloc_pages()在管理區鏈表 zonelist 中依次查找每個區,從中找到滿足要求的區,
然后用伙伴算法從這個區中分配給定大小(2 order 個)的頁面塊。如果所有的區都沒有足夠的
空閑頁面,則調用 swapper 或 bdflush 內核線程,把臟頁寫到磁盤以釋放一些頁面.而且被映射的頁面可能只是做了標記
然后用伙伴算法從這個區中分配給定大小(2 order 個)的頁面塊。如果所有的區都沒有足夠的
空閑頁面,則調用 swapper 或 bdflush 內核線程,把臟頁寫到磁盤以釋放一些頁面.而且被映射的頁面可能只是做了標記
只有在正在需要分配時才分配
struct page * __alloc_pages(unsigned int gfp_mask, unsigned int order, zonelist_t *zonelist)
{
unsigned long min;
zone_t **zone, * classzone;
struct page * page;
int freed;
zone = zonelist->zones;
classzone = *zone;
min = 1UL << order;
for (;;) {//遍歷各種區空閑頁面的總量
zone_t *z = *(zone++);
if (!z)
break;
min += z->pages_low;
if (z->free_pages > min) {//如果總理安大於最低水平線與所請求頁面數之和,就調用rmqueue()試圖分配
page = rmqueue(z, order);
if (page)
return page;//分配成功,返回第一page指針
}
}
如果發現管理區中的空閑頁面總量已經降到最低點,則把 zone_t 結構中需要重新平衡
讓它收回一些頁面以備使用(可以看出, need_balance 是和 kswapd 配合使用的)。
classzone->need_balance = 1;
mb();
if (waitqueue_active(&kswapd_wait))
wake_up_interruptible(&kswapd_wait);
如果給定分配策略中所有頁面管理區都分配失敗,那就把原來的最低水位除以4下調.看能否滿足要求,可以滿足調用rmqueue分配並返回
zone = zonelist->zones;
min = 1UL << order;
for (;;) {
unsigned long local_min;
zone_t *z = *(zone++);
if (!z)
break;
local_min = z->pages_min;
if (!(gfp_mask & __GFP_WAIT))
local_min >>= 2;
min += local_min;
if (z->free_pages > min) {
page = rmqueue(z, order);
if (page)
return page;
}
}
分配還不成功,那就需要看那類進程在請求分配內存頁面,PF_MEMALLOC表示正在分配頁面的進程.
PF_MEMDIE為1表示使用內存溢出而被殺死的進程,這2種必須給進程分配頁面
if (current->flags & (PF_MEMALLOC | PF_MEMDIE)) {
zone = zonelist->zones;
for (;;) {
zone_t *z = *(zone++);
if (!z)
break;
page = rmqueue(z, order);
if (page)
return page;
}
return NULL;
}
分配頁面不可以等待,也不能調度,直接返回,沒有分配到頁面
/* Atomic allocations - we can't balance anything */
if (!(gfp_mask & __GFP_WAIT))
return NULL;
如果必須要得到頁面的進程還還是沒分配到頁面,
就要調用
balance_classzone()函數把當前進程所占有的局部頁面釋放出來。如果釋放成功,則返回一個 page 結構指針,指向頁面塊中第一個頁面的起始地址。
page = balance_classzone(classzone, gfp_mask, order, &freed);
if (page)
return page;
從而繼續分配頁面
以上反復調用了requeue()函數,
該函數試圖從一個頁面管理區分配若干連續的內存頁面。這是最基本的分配操作,其具
體代碼如下:
//指向要分配頁面的管理區,order表示分配頁面數為2的order次方
static struct page * rmqueue(zone_t *zone, unsigned int order)
{
//area指向free_area的第order個元素
free_area_t * area = zone->free_area + order;
unsigned int curr_order = order;
struct list_head *head, *curr;
unsigned long flags;
struct page *page;
spin_lock_irqsave(&zone->lock, flags);
do {
head = &area->free_list;
curr = memlist_next(head);
if (curr != head) {
unsigned int index;
//獲得空閑塊的第 1 個頁面的地址,如果這個地址是個無效的地址,就陷入 BUG()
page = memlist_entry(curr, struct page, list);
if (BAD_RANGE(zone,page))
BUG();
//從隊列中摘除分配出去的頁面塊。
memlist_del(curr);
index = page - zone->zone_mem_map;
if (curr_order != MAX_ORDER-1)
//如果某個頁面塊被分配出去,就要在 frea_area 的位圖中進行標記,這是通過調用 MARK_USED()宏來完成的。
MARK_USED(index, curr_order, area);
zone->free_pages -= 1UL << order;
//如果分配出去后還有剩余塊,就通過 expand()獲得所分配的頁塊,而把剩余塊鏈入適當的空閑隊列中。
page = expand(zone, page, index, order, curr_order, area);
spin_unlock_irqrestore(&zone->lock, flags);
set_page_count(page, 1);
if (BAD_RANGE(zone,page))
BUG();
if (PageLRU(page))
BUG();
if (PageActive(page))
BUG();
return page;
}
curr_order++;
area++;
} while (curr_order < MAX_ORDER);//如果當前空閑隊列沒有空閑塊,就從更大的空閑塊隊列中找。
spin_unlock_irqrestore(&zone->lock, flags);
return NULL;
}
expand體現了伙伴算法
/*
zone指向已分配頁塊所在的管理區
page指向一分配的頁塊
index為一分配的頁塊在mem_map中的下標;
low表示所需頁面塊的大小為2的low次方
high表示從實際空閑隊列中實際分配的頁面塊大小為2的high次方
area指向要實際分配的頁塊
*/
static inline struct page * expand (zone_t *zone, struct page *page,
unsigned long index, int low, int high, free_area_t * area)
{
unsigned long size = 1 << high;//初始化為分配塊的頁面數
while (high > low) {
if (BAD_RANGE(zone,page))
BUG();
area--;
high--;
size >>= 1;
memlist_add_head(&(page)->list, &(area)->free_list);
/*然后調用 memlist_add_head()把剛分配出去的頁面塊又加入到低一檔(物理塊減半)的空閑隊列中准備從剩下的一半空閑塊中重新進
行分配*/
//MARK_USED()設置位圖
MARK_USED(index, high, area);
index += size;
page += size;
}
if (BAD_RANGE(zone,page))
BUG();
return page;
}
Slab的數據結構
對於預期頻繁使用的內存區,可以創建一組
特定大小的專用緩沖區進行處理,以避免內碎片的產生。對於較少使用的內存區,可以創建
一組通用緩沖區(如 Linux 2.0 中所使用的 2 的冪次方)來處理,即使這種處理模式產生碎
片,也對整個系統的性能影響不大。
特定大小的專用緩沖區進行處理,以避免內碎片的產生。對於較少使用的內存區,可以創建
一組通用緩沖區(如 Linux 2.0 中所使用的 2 的冪次方)來處理,即使這種處理模式產生碎
片,也對整個系統的性能影響不大。
slab有2種數據結構:描述緩沖區的結構keme_cache_t,描述Slab的結構kmem_slab_t
slab是slab管理最基本的結構
typedef struct slab_s {
struct list_head list;
unsigned long colouroff;//slab上着色區的大小
void *s_mem; /*指向對象區的起點 */
unsigned int inuse; /* 分配對象的個數 */
kmem_bufctl_t free;//空閑對象鏈的第一個對象
} slab_t;

每個緩沖區管理一個Slab鏈表.Slab按序分3組,第一組全滿的Slab(沒空閑的對象).
第二組只有部分對象被分配,部分對象還空閑,最后一組slab的對象則完全空閑.
為了對slab有效管理,每個緩沖區都有一個輪轉鎖,在對鏈表進行修改時用這鎖進行同步
struct kmem_cache_s {
/* 1) each alloc & free */
/* full, partial first, then free */
struct list_head slabs_full;
struct list_head slabs_partial;
struct list_head slabs_free;
unsigned int objsize;
原始的數據結構的大小.初始化為kemem_cache_t的大小unsigned int flags; /* constant flags */
unsigned int num; //
每個slab obj的個數spinlock_t spinlock;
#ifdef CONFIG_SMP
unsigned int batchcount;
#endif
/* 2) slab additions /removals */
/* order of pgs per slab (2^n) */
unsigned int gfporder;//
gfporder 則表示每個 Slab 大小的對數,即每個 Slab 由 2 gfporder 個頁面構成。/* force GFP flags, e.g. GFP_DMA */
unsigned int gfpflags;
size_t colour; /* 顏色數目 */
unsigned int colour_off; /*顏色的偏移量 */
unsigned int colour_next; /* 下一個slab將要使用的顏色 */
kmem_cache_t *slabp_cache;
unsigned int growing;
unsigned int dflags; /* dynamic flags */
/* constructor func */
void (*ctor)(void *, kmem_cache_t *, unsigned long);
/* de-constructor func */
void (*dtor)(void *, kmem_cache_t *, unsigned long);
unsigned long failures;
/* 3) cache creation/removal */
char name[CACHE_NAMELEN];
struct list_head next;
#ifdef CONFIG_SMP
/* 4) per-cpu data */
cpucache_t *cpudata[NR_CPUS];
#endif
…..
};
定義了 kmem_cache_t
並給部分域賦予了初值,該結構有3個隊列分別指向滿slab,半滿slab,空閑slab.另外一next把所有的專用緩沖區連成一個鏈表
kmem_cache_t 相當於 Slab 的總控結構,
static kmem_cache_t cache_cache = {
slabs_full: LIST_HEAD_INIT(cache_cache.slabs_full) ,
slabs_partial: LIST_HEAD_INIT(cache_cache.slabs_partial),
slabs_free: LIST_HEAD_INIT(cache_cache.slabs_free) ,
objsize: sizeof(kmem_cache_t),//原始的數據結構的大小.初始化為kemem_cache_t的大小
flags: SLAB_NO_REAP,
spinlock: SPIN_LOCK_UNLOCKED,
colour_off: L1_CACHE_BYTES,
name: "kmem_cache",
};

專用緩沖區的建立與撤銷
//緩沖區名 對象大小 所請求的着色偏移量
kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t offset,
unsigned long c_flags,//對緩沖區的設置標志,SLAB_HWCACHE_ALIGN:表示與第一個高速緩沖中的行邊界對齊
//指向對象指針 ,指向緩沖區
void (*ctor) (void *objp, kmem_cache_t *cachep, unsigned long flags),//構造函數,一般為NULL
void (*dtor) (void *objp, kmem_cache_t *cachep, unsigned long flags))//析構函數一般為NULL
kmem_cache_create()函數要進行一系列的計算,以確定最佳的 Slab 構成。包括:每
個 Slab 由幾個頁面組成,划分為多少個對象;Slab 的描述結構 slab_t 應該放在 Slab 的外
面還是放在 Slab 的尾部;還有“顏色”的數量等等。並根據調用參數和計算結果設置
kmem_cache_t 結構中的各個域,包括兩個函數指針 ctor 和 dtor。最后,將 kmem_cache_t
結構插入到 cache_cache 的 next 隊列中。
kmem_cache_create()所創建的緩沖區中還沒有包含任何 Slab,因此,
也沒有空閑的對象。只有以下兩個條件都為真時,才給緩沖區分配 Slab:
(1)已發出一個分配新對象的請求;
(2)緩沖區不包含任何空閑對象。
當這兩個條件都成立時,Slab 分配模式就調用 kmem_cache_grow()函數給緩沖區分配
一個新的 Slab。其中,該函數調用 kmem_gatepages()從伙伴系統獲得一組頁面;然后又調用
kmem_cache_slabgmt()獲得一個新的 Slab 結構; 還要調用 kmem_cache_init_objs()為新 Slab
中的所有對象申請構造方法(如果定義的話); 最后, 調用 kmem_slab_link_end()把這個 Slab
結構插入到緩沖區中 Slab 鏈表的末尾。
個 Slab 由幾個頁面組成,划分為多少個對象;Slab 的描述結構 slab_t 應該放在 Slab 的外
面還是放在 Slab 的尾部;還有“顏色”的數量等等。並根據調用參數和計算結果設置
kmem_cache_t 結構中的各個域,包括兩個函數指針 ctor 和 dtor。最后,將 kmem_cache_t
結構插入到 cache_cache 的 next 隊列中。
kmem_cache_create()所創建的緩沖區中還沒有包含任何 Slab,因此,
也沒有空閑的對象。只有以下兩個條件都為真時,才給緩沖區分配 Slab:
(1)已發出一個分配新對象的請求;
(2)緩沖區不包含任何空閑對象。
當這兩個條件都成立時,Slab 分配模式就調用 kmem_cache_grow()函數給緩沖區分配
一個新的 Slab。其中,該函數調用 kmem_gatepages()從伙伴系統獲得一組頁面;然后又調用
kmem_cache_slabgmt()獲得一個新的 Slab 結構; 還要調用 kmem_cache_init_objs()為新 Slab
中的所有對象申請構造方法(如果定義的話); 最后, 調用 kmem_slab_link_end()把這個 Slab
結構插入到緩沖區中 Slab 鏈表的末尾。
通用緩沖區
在內核中初始化開銷不大的數據結構可以合用一個通用的緩沖區。通用緩沖區非常類似
於物理頁面分配中的大小分區,最小的為 32,然后依次為 64、128、……直至 128KB(即 32
個頁面),但是,對通用緩沖區的管理又采用的是 Slab 方式。從通用緩沖區中分配和釋放緩
沖區的函數為:
在內核中初始化開銷不大的數據結構可以合用一個通用的緩沖區。通用緩沖區非常類似
於物理頁面分配中的大小分區,最小的為 32,然后依次為 64、128、……直至 128KB(即 32
個頁面),但是,對通用緩沖區的管理又采用的是 Slab 方式。從通用緩沖區中分配和釋放緩
沖區的函數為:
void *kmalloc(size_t size, int flags);
Void kree(const void *objp);
因此,當一個數據結構的使用根本不頻繁時,或其大小不足一個頁面時,就沒有必要給
其分配專用緩沖區,而應該調用 kmallo()進行分配。如果數據結構的大小接近一個頁面,則
干脆通過 alloc_page()為之分配一個頁面。
事實上,在內核中,尤其是驅動程序中,有大量的數據結構僅僅是一次性使用,而且所
占內存只有幾十個字節, 因此, 一般情況下調用 kmallo()給內核數據結構分配內存就足夠了。
另外,因為,在 Linux 2.0 以前的版本一般都調用 kmallo()給內核數據結構分配內存,因此,
調用該函數的一個優點是(讓你開發的驅動程序)能保持向后兼容。
其分配專用緩沖區,而應該調用 kmallo()進行分配。如果數據結構的大小接近一個頁面,則
干脆通過 alloc_page()為之分配一個頁面。
事實上,在內核中,尤其是驅動程序中,有大量的數據結構僅僅是一次性使用,而且所
占內存只有幾十個字節, 因此, 一般情況下調用 kmallo()給內核數據結構分配內存就足夠了。
另外,因為,在 Linux 2.0 以前的版本一般都調用 kmallo()給內核數據結構分配內存,因此,
調用該函數的一個優點是(讓你開發的驅動程序)能保持向后兼容。
內核空間非連續內存區的管理
非連續內存處於3G到4G(虛擬地址)

PAGE_OFFSET為3G.high_memory表示保存物理地址的最高值的變量.VMALLOC_START為非連續區的起始地址
在物理地址的末尾與第一個內存區之間插入了一個 8MB(VMALLOC_OFFSET)的區間
這
是一個安全區,目的是為了“捕獲”對非連續區的非法訪問。出於同樣的理由,在其他非連
續的內存區之間也插入了 4KB 大小的安全區。每個非連續內存區的大小都是 4096 的倍數。
描述非連續區的數據結構
是一個安全區,目的是為了“捕獲”對非連續區的非法訪問。出於同樣的理由,在其他非連
續的內存區之間也插入了 4KB 大小的安全區。每個非連續內存區的大小都是 4096 的倍數。
描述非連續區的數據結構
struct vm_struct {
unsigned long flags;
void * addr;//內存區的起始地址
unsigned long size;//內存區大小+4096(安全區的大小)
struct vm_struct * next;
};
struct vm_struct * vmlist;//非連續區組成一個單鏈表