第八章 内存管理

8.1:页框管理

8.1.1:页描述符

       页框,也就是物理内存上的4K或者其它大小的物理页。内核必须记录每个页框的当前状态。这些状态信息保存在一个page结构体中,这个结构体叫做页描述符。

struct page {

       page_flags_t flags;        //每个二进制位都描述了一个页的状态

       atomic_t _count;          //页的引用计数器,如果这个值为-1,表示这个页框空闲;count+1表示这个页框的使用者数目

       atomic_t _mapcount;         

       unsigned long private;         //伙伴系统中,一个大小为2^n的空闲页框块的第一个页描述符的private存储值n。当一个空闲页框块释放的时候,这个值存储n,会以此判断相邻大小为2^n的页框块是否空闲。如果空闲,就合并空闲页框块

       struct address_space *mapping;

       pgoff_t index;             

       struct list_head lru;              //伙伴系统中,一个大小为2^n的空闲页框块的第一个页描述符的lru链接着free_area中的链表头。当然,如果这个页不是空闲页框的第一个页,这个成员存储的内容表示其他含义。

#if defined(WANT_PAGE_VIRTUAL)

       void *virtual;               

#endif /* WANT_PAGE_VIRTUAL */

};

       所有的页描述符存放在mem_map数组中。

struct page *mem_map;

8.1.2:非一致性内存访问

       非一致性内存访问模型:在这种模型下,给定CPU对不同的内存单元的访问用时可能不同。系统的物理内存被划分成几个物理节点,同时,每个节点又被分成数个管理区。在一个节点内,任意给定CPU访问页面需要的时间都是相等的。在x86系统,IBM兼容PC中,所有的物理内存被组织成一个节点,然后使用链表头链接起来。

struct pglist_data *pgdat_list;

       节点的数据结构如下:

typedef struct pglist_data {

       struct zone node_zones[MAX_NR_ZONES];   //节点中管理区描述符数组

       struct zonelist node_zonelists[GFP_ZONETYPES];  //用于表示分配页框时首选的管理区

       int nr_zones;

       struct page *node_mem_map;

       struct bootmem_data *bdata;

       unsigned long node_start_pfn;

       unsigned long node_present_pages; /* total number of physical pages */

       unsigned long node_spanned_pages; /* total size of physical page

                                        range, including holes */

       int node_id;

       struct pglist_data *pgdat_next;

       wait_queue_head_t kswapd_wait;

       struct task_struct *kswapd;

       int kswapd_max_order;

} pg_data_t;

8.1.3:内存管理区

       每个内存节点被分成三个内存管理区。在x86结构中,分别是:

       ZONE_DMA:包含低于16M的页框;

       ZONE_NORMAL:包含高于16M,低于896M的页框;

ZONE_HIGHMEM:包含高于896M的页框;

       ZONE_DMA 的划分原因是,ISA总线的直接内存存取(DMA)处理器只能对内存的前16M进行寻址。ZONE_DMA和ZONE_NORMAL这些物理页框直接映射到线性地址空间的第4个GB,可以被内核直接使用。但是,ZONE_HIGHMEM不能由内核直接访问,事实上,高于896M的物理内存必须使用高端内存页框的内核映射方法进行映射。

       每个内存管理区都有自己的描述符,见结构体zone:

struct zone {

       /* Fields commonly accessed by the page allocator */

       unsigned long             free_pages;

       unsigned long             pages_min, pages_low, pages_high;   // pages_min存储了管理区中保留页框的数目,pages_low和pages_high分别是回收页框使用的下界或者上界,也在管理区分配器中作为阈值使用

       unsigned long             lowmem_reserve[MAX_NR_ZONES];   //在内存不足的情况下,每个管理区必须保留的页框数目

       struct per_cpu_pageset pageset[NR_CPUS];

       spinlock_t             lock;

       struct free_area     free_area[MAX_ORDER];

       ZONE_PADDING(_pad1_)

       spinlock_t             lru_lock;

       struct list_head     active_list;

       struct list_head     inactive_list;

       unsigned long             nr_scan_active;

       unsigned long             nr_scan_inactive;

       unsigned long             nr_active;

       unsigned long             nr_inactive;

       unsigned long             pages_scanned;       /* since last reclaim */

       int                 all_unreclaimable; /* All pages pinned */

       int temp_priority;

       int prev_priority;

       ZONE_PADDING(_pad2_)

       wait_queue_head_t      * wait_table;

       unsigned long             wait_table_size;

       unsigned long             wait_table_bits;

       struct pglist_data  *zone_pgdat;

       struct page           *zone_mem_map;

       unsigned long             zone_start_pfn;

       unsigned long             spanned_pages;    /* total size, including holes */

       unsigned long             present_pages;     /* amount of memory (excluding holes) */

       char              *name;

} ____cacheline_maxaligned_in_smp;

       此外,还需要建立页描述符到内存节点和管理区的链接。在这里不直接使用指针的方式,目的是为了节省空间。而是将相应的内存节点和管理区编码后放在page结构体的flags字段中。

8.1.4:保留的页框池

       在请求内存的时候,如果有足够的空闲内存,请求就会立即满足;否则的话,将发出请求的内核控制路径阻塞,直到有内存被释放。

       但是在某些请求中,无法阻塞内存控制路径(例如在中断或者在临界区代码中)。这时候页框分配会失败并且返回。但是,我们希望这种情况尽量少发生,尽量保证有内存可以分配。因此,我们引入保留的页框池。使用参数min_free_kbytes表示:

int min_free_kbytes = 1024;   //1024/4=256,因此总共有25个页框用作保留的页框池

       ZONE_DMA和ZONE_NORMAL将他们的一部分页框用于保留的页框池,只有在不能阻塞的内核控制路径中,会被使用到。注意,保留的页框池将会全部被直接映射到内核线性地址空间中,因为只有在内核控制路径上,进行页框分配的时候会使用到这个页框池。

在管理区描述符的page_min字段,就表示了管理区内保留页框的数目。

8.1.5:分区页框分配器

被称为分区页框分配器的内核子系统用处理连续页框分配请求。

        在请求分配的时候,分配器搜索一个包含能够满足要求的连续页框的管理区。在每个管理区中,使用伙伴系统处理页框。还有一小部分页框保留在高速缓存中,用于快速满足对单个页框的分配请求。

 

8.1.5.1:请求和释放页框

       使用多个函数分配或者释放页框。这些函数将在后面分析。

8.1.6:高端内存页框的内核映射

       全局变量high_memory用于表示直接映射的物理内存末端,高端内存的始端。高端内存的页框并不会直接映射在内核线性地址空间中(虚拟地址3~4G,实际为3~3G+896M)。因此,内核不能直接访问高端内存。

在32位X86系统中,为了能够使用高端内存,需要做以下处理:

1:高端内存页框通过alloc_pages()或者alloc_page()分配。他们不返回页框的线性地址。因为高端内存的页框的线性地址不存在。而是返回分配的页框的页描述符的线性地址。

       2:因为高端内存的页框没有线性地址,不能被内核访问。所以,内核线性地址空间的最后128M专门用于映射高端内存页框。不同的高端内存都会使用这128M的线性地址。

       内核使用三种不同的机制将页框映射到高端内存,分别是永久内核映射,临时内核映射,非连续内存分配。

8.1.6.1:永久内核映射

       永久内核映射用于建立高端页框到内核地址空间的长期映射。建立永久内核映射的过程可能发生阻塞。因此,不能在中断或者临界区中建立永久内核映射。

永久内核映射建立的页表地址存放在全局变量pkmap_page_table中

pte_t *pkmap_page_table;    //总共是LAST_PKMAP(512或者1024)个页表项

       此外,还使用数组

static int pkmap_count[LAST_PKMAP];  //和pkmap_page_table中的成员是一一对应关系,数值用于描述pkmap_page_table每个pte的状态。0表示这个pte是空闲的,没有映射高端内存;1表示这个pte没有映射高端内存,但是他是不能使用的,因为其对应的TLB还没有刷新;n表示这个pte映射了高端内存,并且有n-1个内核路径在使用这个pte。

来辅助描述永久内核映射页表。

       除此之外,还是用全局数组page_address_htable来描述已经被映射的高端内存。在系统中的组织结构如下图所示:

        page_address()函数用于返回页框对应的线性地址。由于高端内存的页框没有对应的线性地址,因此会返回高端内存页框对应的页描述符的线性地址。

void *page_address(struct page *page)

{

       unsigned long flags;

       void *ret;

       struct page_address_slot *pas;

       if (!PageHighMem(page))    //检查page描述符结构体的成员flags是否设置了PG_highmem位

              return lowmem_page_address(page);

       pas = page_slot(page);

       ret = NULL;

       spin_lock_irqsave(&pas->lock, flags);

       if (!list_empty(&pas->lh)) {

              struct page_address_map *pam;

              list_for_each_entry(pam, &pas->lh, list) {    //如果寻找的页框是高端内存,并且没有被映射,那么这个页框不会被加到全局变量page_address_htable中,也就不会在链表操作中被找到,就会返回NULL。如果找到,就说明这个高端内存页框已经被映射,那么就返回这个高端页框映射的虚拟地址

                     if (pam->page == page) {

                            ret = pam->virtual;

                            goto done;

                     }

              }

       }

done:

       spin_unlock_irqrestore(&pas->lock, flags);

       return ret;

}

       介绍一下函数lowmem_page_address

static inline void *lowmem_page_address(struct page *page)

{

       return __va(page_to_pfn(page) << PAGE_SHIFT);   //<< PAGE_SHIFT就是乘4096

}

#define page_to_pfn(page) ((unsigned long)((page) - mem_map))   //两个指针相减,得到的结果是这两个指针之间数组元素的个数

要建立高端内存的永久内核映射,需要使用kmap()函数。

void *kmap(struct page *page)

{

       if (!PageHighMem(page))

              return page_address(page);    //如果这个页框不属于高端内存,那么就直接返回这个页框的虚拟地址

       return kmap_high(page);

}

void fastcall *kmap_high(struct page *page)

{

       unsigned long vaddr;

       spin_lock(&kmap_lock);    //这里要加锁,避免多个CPU都修改高端内存页框对应的页表。但是这里不需要禁止中断。因为限制中断中不能使用kmap函数,因为这个函数是可阻塞的。

       vaddr = (unsigned long)page_address(page);

       if (!vaddr)    //判断这个页框是否已经被映射过了

              vaddr = map_new_virtual(page);   //页框没有被映射过

       pkmap_count[PKMAP_NR(vaddr)]++;

       spin_unlock(&kmap_lock);

       return (void*) vaddr;

}

       map_new_virtual此函数的主要操作是,检查pkmap_page_table中是否有空闲的页表项位置(此页表专门用于存储高端内存的映射关系)。如果有的话,那么就填入这个位置;如果没有的话,就将进程加入等待队列,直到另一个进程释放这个高端内存页表中的一个位置,然后换新等待队列中的进程。

static inline unsigned long map_new_virtual(struct page *page)

{

       unsigned long vaddr;

       int count;

start:

       count = LAST_PKMAP;          //1024

       /* Find an empty entry */

       for (;;) {

              last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;//这个位操作的作用是,当last_pkmap_nr<512时,保持他的值;等于512时,变成0。

              if (!last_pkmap_nr) {       //如果last_pkmap_nr的值变成0的时候,这时候应该将高端内存对应的页表项中,无效的项刷出去。

                     flush_all_zero_pkmaps();

                     count = LAST_PKMAP;

              }

              if (!pkmap_count[last_pkmap_nr])    //找到了空闲位置。之前说过,pkmap_count数组中某一项为0表示高端内存页表中对应的页表项没有使用

                     break;    

              if (--count)

                     continue

              {    //如果没有找到空闲的页表项,那么将当前任务加入等待队列

                     DECLARE_WAITQUEUE(wait, current);

                     __set_current_state(TASK_UNINTERRUPTIBLE);

                     add_wait_queue(&pkmap_map_wait, &wait);   //将当前任务挂在pkmap_map_wait等待队列上

                     spin_unlock(&kmap_lock);

                     schedule();

                     remove_wait_queue(&pkmap_map_wait, &wait);   //当有任务释放了高端页表中的页表项的时候,会唤醒挂在这个等待队列上的任务

                     spin_lock(&kmap_lock);

                     /* Somebody else might have mapped it while we slept 可能有其他进程已经映射了*/

                     if (page_address(page))

                            return (unsigned long)page_address(page);

                     /* Re-start */

                     goto start;

              }

       }

//找到位置的情况,将其加入高端内存页表中,此时获得的vaddr是高端内存页表中,对应页表项的虚拟地址

       vaddr = PKMAP_ADDR(last_pkmap_nr);   //这里是找到序号为last_pkmap_nr的页表项对应的虚拟地址,为(PKMAP_BASE + ((nr) << PAGE_SHIFT)),其中PKMAP_BASE是高端内存页表开始映射的虚拟地址首地址。高端内存页表最多映射4M内存。

       set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));   // mk_pte(page, kmap_prot)的作用是作出高端页表项

       pkmap_count[last_pkmap_nr] = 1;

       set_page_address(page, (void *)vaddr);   //这个高端内存页框已经完成映射,将他加到哈希表中。比较复杂,但是一眼能看懂,就不列出了。

       return vaddr;

}

       这里也是一个使用等待队列的典型例子。

DECLARE_WAITQUEUE(wait, current);

#define DECLARE_WAITQUEUE(name, tsk)                                  \

       wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

#define __WAITQUEUE_INITIALIZER(name, tsk) {                          \

       .task       = tsk,                                        \

       .func              = default_wake_function,                  \

       .task_list = { NULL, NULL } }

       因此,展开后,就是

wait_queue_t wait =

      {.task = current,

       .func = default_wake_function,

       .task_list = {NULL, NULL} }

同样,有建立虚拟地址和高端内存映射的接口,也有取消虚拟地址和高端内存映射的接口。通过函数kunmap可以实现这个功能。

void kunmap(struct page *page)

{

       if (!PageHighMem(page))

              return;

       kunmap_high(page);    //必须是属于高端内存的页框才能进入这个函数

}

       如果确认是高端内存的页框,那么就会调用函数kunmap_high

void fastcall kunmap_high(struct page *page)

{

       unsigned long vaddr;

       unsigned long nr;

       int need_wakeup;

       spin_lock(&kmap_lock);

       vaddr = (unsigned long)page_address(page);

       nr = PKMAP_NR(vaddr);     //通过这个高端内存的虚拟地址,得到他是对应高端内存页表中的第几个页表项。这里需要注意的是,由于高端内存页框的映射方式,可知道不同的高端内存页框的虚拟地址是不连续的。

       /*

        * A count must never go down to zero

        * without a TLB flush!

        */

       need_wakeup = 0;

       switch (--pkmap_count[nr]) {   //获得这个高端内存页框的页表项的使用个数

       case 0:

              BUG();

       case 1:

              need_wakeup = waitqueue_active(&pkmap_map_wait);

       }

       spin_unlock(&kmap_lock);

       /* do wake-up, if needed, race-free outside of the spin lock */

       if (need_wakeup)

              wake_up(&pkmap_map_wait);    //唤醒挂在这个等待队列上的进程

}

8.1.6.2:临时内核映射

       还可以通过临时内核映射来建立内核到高端内存的页表项。并且由于他们不会阻塞的特性,可以用在中断处理程序和可延迟函数中。

       通过函数kmap_atomic建立临时内核映射。

void *kmap_atomic(struct page *page, enum km_type type)

{

       enum fixed_addresses idx;

       unsigned long vaddr;

       inc_preempt_count();

       if (!PageHighMem(page))

              return page_address(page);

       idx = type + KM_TYPE_NR*smp_processor_id();

       vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);

       set_pte(kmap_pte-idx, mk_pte(page, kmap_prot));

       __flush_tlb_one(vaddr);

       return (void*) vaddr;

}

8.1.7:伙伴系统算法

       页框的分配过程中,会出现外碎片的问题。外碎片就是,随着频繁的请求和释放一系列大小不同的页框,会导致在已分配的页框中分散了许多小块的空闲页框。因此,可能系统中剩余的空闲页框的大小大于要分配的页框大小,但是由于不连续,已经无法分配了。

为了解决外碎片的问题,有两种方法,一是分配不连续的空闲页框,将他们映射到连续的线性地址空间,一次满足要求;而是通过伙伴算法这一类算法,避免外碎片的大量产生。

Linux使用伙伴系统算法来解决外碎片的问题。他将所有的空闲页框分组为11个链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续的页框,也就是4K~4M大小的连续页框。

8.1.7.1:数据结构

       每个管理区使用不同的伙伴系统。因此对于x86而言,有三个内存管理区,因此也就有三个伙伴系统。分别是适用于ISA DMA(0~16M)的伙伴系统;适用于常规页框(16M~896M)的伙伴系统;适用于高端内存的伙伴系统(896M~4G)。每个伙伴系统使用的数据结构如下所示(都包含在管理区描述符中):

struct zone {

       unsigned long             free_pages;

       unsigned long             pages_min, pages_low, pages_high;

       unsigned long             lowmem_reserve[MAX_NR_ZONES];

       struct per_cpu_pageset pageset[NR_CPUS];    //用于实现每CPU高速缓存

       spinlock_t             lock;

       struct free_area     free_area[MAX_ORDER];    //每个元素都是一个链表头,链表链接了不同大小的空闲页框组成的块。第n个元素,就是大小为2^n的空闲页框块链表的链表头

       ZONE_PADDING(_pad1_)

       spinlock_t             lru_lock;

       struct list_head     active_list;

       struct list_head     inactive_list;

       unsigned long             nr_scan_active;

       unsigned long             nr_scan_inactive;

       unsigned long             nr_active;

       unsigned long             nr_inactive;

       unsigned long             pages_scanned;       /* since last reclaim */

       int                 all_unreclaimable; /* All pages pinned */

       int temp_priority;

       int prev_priority;

       ZONE_PADDING(_pad2_)

       wait_queue_head_t      * wait_table;

       unsigned long             wait_table_size;

       unsigned long             wait_table_bits;

       struct pglist_data  *zone_pgdat;

       struct page           *zone_mem_map;

       /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */

       unsigned long             zone_start_pfn;

       unsigned long             spanned_pages;    /* total size, including holes */

       unsigned long             present_pages;     /* amount of memory (excluding holes) */

       char              *name;

} ____cacheline_maxaligned_in_smp;

       1:管理区把包含的页框。每个管理区包含的页框都是mem_map的子集。struct page *zone_mem_map:表示了这个管理区包含的第一个页框描述符;

       2:管理区描述符中struct free_area free_area[MAX_ORDER]:每个元素对应的就是一种块大小。比如第k个元素,对应的就是包含2^k个页框的空闲块。

struct free_area {

       struct list_head     free_list;   //连接了空闲页框块的第一个页描述符的lru成员

       unsigned long      nr_free;    //指定了这个链表头链接的链表节点个数,其实就是,这个大小的空闲页框块的个数

};

他的free_list指向空闲块的第一个页描述符的lru。第一个页描述符的lru的prev指向free_list,next指向第二个空闲块的起始页的页描述符的lru的prev。这样就是用一个双向链表将所有大小为2^k的空闲内存块的起始页的页描述符联系了起来。

8.1.7.2:分配块

       使用函数

static struct page *__rmqueue(struct zone *zone, unsigned int order)

从管理区中分配一个空闲块。这个函数使用时,默认已经关中断,并且获得了内存管理区的锁。

static struct page *__rmqueue(struct zone *zone, unsigned int order)

{

       struct free_area * area;

       unsigned int current_order;

       struct page *page;

       for (current_order = order; current_order < MAX_ORDER; ++current_order) {

              area = zone->free_area + current_order;  //拿到order对应的数组成员,这个数组成员是struct free_area类型的结构体

              if (list_empty(&area->free_list))

                     continue;

              page = list_entry(area->free_list.next, struct page, lru);     //刚才已经说了,freee_list链表链接的是每个页描述符的lru。因此,找到满足要求的空闲页框块的第一个页的页描述符

              list_del(&page->lru);

              rmv_page_order(page);    //其实就是将页描述符的private成员设置为0

              area->nr_free--;

              zone->free_pages -= 1UL << order;

              return expand(zone, page, order, current_order, area);//这个函数的作用是,如果是从更大的空闲内存块分成数个小的空闲内存块,那么要将小的内存块加入对应的空闲内存块链表中

       }

       return NULL;

}

在这里分析一下函数expand。假设某一次high的值为8,low的值为2。也就是说,我们本来需要4个连续页框,但是没有这么小的连续页框了,只能从256个连续页框中分出4个页框出来。

static inline struct page *expand(struct zone *zone, struct page *page,

      int low, int high, struct free_area *area)    // low:这次分配的2^n个页框的这个n值;high:这次是从2^n个空闲页框中分出来的,这个n的值

{

       unsigned long size = 1 << high;   //size=256

       while (high > low) {

              area--;    //area是数组地址,最开始是第7个,后面依次变为6,5……

              high--;

              size >>= 1;

              list_add(&page[size].lru, &area->free_list);    //第一次size=256,因此会将page+256表示的页描述符放入area中,从此可知,每次分出去的页框都是在前面的页框

              area->nr_free++;

              set_page_order(&page[size], high);   //设置空闲页框块的第一个页描述符的private字段为order

       }

       return page;

}

8.1.7.3:释放块

使用函数

static inline void __free_pages_bulk (struct page *page, struct page *base, struct zone *zone, unsigned int order)

释放块。

       同样,假设我们要释放的是内存管理区ISA DMA(0~16M)中的页框块,是从8~10M的大小为2M的页框块,并且假设其他页框块都是空闲的。

static inline void __free_pages_bulk (struct page *page, struct page *base,

              struct zone *zone, unsigned int order)  //page是被释放页框块的第一个页描述符,zone是管理区描述符地址,order是块大小,base是这个管理区的第一个页框的页框描述符

{

       unsigned long page_idx;

       struct page *coalesced;

       int order_size = 1 << order;   //order_size=512

       page_idx = page - base;   // page_idx表明这个页框是这个管理区的第几个页框,page_index=2048

       zone->free_pages += order_size;

       while (order < MAX_ORDER-1) {

              struct free_area *area;

              struct page *buddy;

              int buddy_idx;

              buddy_idx = (page_idx ^ (1 << order));   //使用(1<<order)异或page_idx,就是将page_index的order位设置为当前的相反值。因此,如果这个位原先是0,那么buddy_idx=page_index+order_size;否则, buddy_idx=page_index-order_size

              buddy = base + buddy_idx;

              if (bad_range(zone, buddy))   //如果伙伴页框不在这个管理区中,那么跳出

                     break;

              if (!page_is_buddy(buddy, order)) //如果这个页不能当做伙伴页,那么跳出

                     break;

              /* Move the buddy up one level. */

              list_del(&buddy->lru);   //将伙伴页框从空闲页框块链表中删除,因为伙伴页框要和我们释放的页框一起,作为一个更大的页框,添加到页框链表中了

              area = zone->free_area + order;

              area->nr_free--;

              rmv_page_order(buddy);

              page_idx &= buddy_idx;

              order++;

       }

       coalesced = base + page_idx;   //将组成后的大页添加到页框链表中

       set_page_order(coalesced, order);

       list_add(&coalesced->lru, &zone->free_area[order].free_list);

       zone->free_area[order].nr_free++;

}

8.1.8:每CPU页框高速缓存

       内核经常请求或者释放单个页框。为此,每个内存管理区都定义了一个每CPU页框高速缓存。所有的每CPU高速缓存都包含一些预先分配的页框,用于处理CPU发出的单一内存请求。

       还是要注意,每CPU高速缓存的页框是从伙伴系统中分出来的。

struct zone {

       ……

       struct per_cpu_pageset pageset[NR_CPUS];

……

}

struct per_cpu_pageset {

       struct per_cpu_pages pcp[2];      //0: hot。1: cold,分别表示热高速缓存和冷高速缓存

} ____cacheline_aligned_in_smp;

struct per_cpu_pages {

       int count;              /* 高速缓存中页框个数 */

       int low;          /* 下界,表示高速缓存需要补充 */

       int high;         /* 上界,表示高速缓存需要释放*/

       int batch;       /*每次添加或者删除页框的数目*/

       struct list_head list;       //链表头,链表链接的是所有属于此高速缓存的页的页描述符,依然是连接到页描述符的lru上。

};

       在冷热高速缓存中,如果页框个数低于low,那么内核通过伙伴系统分配batch个页框到高速缓存中;如果页框个数高于high,那么内核从高速缓存中释放batch个页框到伙伴系统中。

8.1.8.1:通过每CPU高速缓存分配页框

       使用函数

struct page *buffered_rmqueue(struct zone *zone, int order, int gfp_flags)

来分配页框。这个函数是伙伴系统的核心,它既能从每CPU高速缓存中分配页框,也可以从高速缓存中分配页框。

static struct page *buffered_rmqueue(struct zone *zone, int order, int gfp_flags)

{

       unsigned long flags;

       struct page *page = NULL;

       int cold = !!(gfp_flags & __GFP_COLD);    // gfp_flags是分配标志

       if (order == 0) {    //只有在分配一个页框的时候,使用每CPU高速缓存

              struct per_cpu_pages *pcp;

              pcp = &zone->pageset[get_cpu()].pcp[cold];//这里确定是从冷还是热高速缓存中分配页框

              local_irq_save(flags);

              if (pcp->count <= pcp->low)     //如果现在高速缓存中的页数小于下界,那么需要向高速缓存中添加页

                     pcp->count += rmqueue_bulk(zone, 0, pcp->batch, &pcp->list);      //利用伙伴系统,从内存管理区中找到batch页添加到高速缓存中

              if (pcp->count) {

                     page = list_entry(pcp->list.next, struct page, lru);   //这里从高速缓存中拿到一页

                     list_del(&page->lru);  

                     pcp->count--;

              }

              local_irq_restore(flags);

              put_cpu();    //开抢占,为什么开抢占不清楚

       }

       if (page == NULL) {//如果要分配的页数不止1页,或者是1页,但是之前的操作没有完成分配的时候,直接在伙伴系统中分配

              spin_lock_irqsave(&zone->lock, flags);

              page = __rmqueue(zone, order);//使用伙伴系统进行页框的分配

              spin_unlock_irqrestore(&zone->lock, flags);

       }

       if (page != NULL) {         //这时候已经分配成功

              mod_page_state_zone(zone, pgalloc, 1 << order);

              prep_new_page(page, order);

              if (gfp_flags & __GFP_ZERO)

                     prep_zero_page(page, order, gfp_flags);

              if (order && (gfp_flags & __GFP_COMP))

                     prep_compound_page(page, order);

       }

       return page;

}

       分析内部函数rmqueue_bulk,它的目的是从伙伴系统中,分出batch个页框给每CPU高速缓存。

static int rmqueue_bulk(struct zone *zone, unsigned int order,unsigned long count, struct list_head *list)

{

       unsigned long flags;

       int i;

       int allocated = 0;

       struct page *page;

      

       spin_lock_irqsave(&zone->lock, flags);

       for (i = 0; i < count; ++i) {    //每次分配一页,分配count,也就是batch次

              page = __rmqueue(zone, order);

              if (page == NULL)

                     break;

              allocated++;

              list_add_tail(&page->lru, list);   //将分配出来的页添加到每CPU高速缓存链表中,list=zone->pageset[cpu_id].pcp[cold]->list

       }

       spin_unlock_irqrestore(&zone->lock, flags);

       return allocated;

}

8.1.8.2:释放页框到每CPU页框高速缓存

static void fastcall free_hot_cold_page(struct page *page, int cold)

{

       struct zone *zone = page_zone(page);//获得相应的内存管理区描述符地址

       struct per_cpu_pages *pcp;

       unsigned long flags;

       arch_free_page(page, 0);

       kernel_map_pages(page, 1, 0);

       inc_page_state(pgfree);

       if (PageAnon(page))

              page->mapping = NULL;

       free_pages_check(__FUNCTION__, page);

       pcp = &zone->pageset[get_cpu()].pcp[cold];

       local_irq_save(flags);

       if (pcp->count >= pcp->high)

              pcp->count -= free_pages_bulk(zone, pcp->batch, &pcp->list, 0); //释放每CPU高速缓存中的多余页,将之添加进伙伴系统中

       list_add(&page->lru, &pcp->list);//将本次要释放的页添加到每CPU高速缓存中

       pcp->count++;

       local_irq_restore(flags);

       put_cpu();

}

8.1.9:管理区分配器

       管理区分配器是内核页框分配器的前端。也就是说,在分配页框的时候,首先要找到一个适合的管理区,然后再在此管理区中,通过页框管理器分配页框。

对一组连续页框的每次请求实质上是通过执行alloc_pages宏来处理的。这个宏实际上调用了函数:

struct page * fastcall __alloc_pages(unsigned int gfp_mask, unsigned int order, struct zonelist *zonelist)

gfp_mask:内存分配的标志,包含是否在冷热CPU高速缓存页框中分配的标志位

order:表示要分配2^order个页框

zonelist:指向zonelist的数据结构,该数据结构按照优先次序描述了适用于内存分配的内存管理区,他的数据结构如下

struct zone *zones[MAX_NUMNODES * MAX_NR_ZONES + 1];

需要注意的是参数struct zonelist *zonelist,他表示了用于内存分配的内存管理区的优先次序。

       首先要理解函数zone_watermark_ok

int zone_watermark_ok(struct zone *z, int order, unsigned long mark,

                    int classzone_idx, int can_try_harder, int gfp_high)

{

       long min = mark, free_pages = z->free_pages - (1 << order) + 1;

       int o;

       if (gfp_high)

              min -= min / 2;

       if (can_try_harder)

              min -= min / 4;

//上面共同确定min的值

// 要求管理区剩下的页框数目必须大于min+保留的页框数

       if (free_pages <= min + z->lowmem_reserve[classzone_idx])                  

              return 0;

//除了被分配的页框,在order为k的块中,管理区至少有min/2^k个空闲页框。这段代码很容易看懂,但不是很好描述

       for (o = 0; o < order; o++) {

              /* At the next order, this order's pages become unavailable */

              free_pages -= z->free_area[o].nr_free << o;

              /* Require fewer higher order pages to be free */

              min >>= 1;

              if (free_pages <= min)

                     return 0;

       }

       return 1;

}

       通过下面的函数__alloc_pages可以看到,只有当函数zone_watermark_ok返回1的时候,才能够分配页框。zone_watermark_ok会依据不同的参数确定一个最小值min。只有在以下情况,函数会返回1:

       1:除了要分配的页框外,在内存管理区中至少还有min个空闲页框,并且这min个空闲页框不能包含为内存不足保留的页框(lowmem_reserve字段描述)。

       2:除了被分配的页框,在order为k的块中,管理区至少有min/2^k个空闲页框。

       可以看到,上面的函数也是按照这样的要求编写的。

       再看函数__alloc_pages,这个函数的逻辑就是,找到一个合适的内存管理区,然后在这个内存管理区中分配需要的页框。

struct page * fastcall __alloc_pages(unsigned int gfp_mask, unsigned int order,

              struct zonelist *zonelist)

{

       const int wait = gfp_mask & __GFP_WAIT;

       struct zone **zones, *z;

       struct page *page;

       struct reclaim_state reclaim_state;

       struct task_struct *p = current;

       int i;

       int classzone_idx;

       int do_retry;

       int can_try_harder;

       int did_some_progress;

       might_sleep_if(wait);

       can_try_harder = (unlikely(rt_task(p)) && !in_interrupt()) || !wait;

       zones = zonelist->zones;  //可以分配的内存管理区构成的数组的首地址

       classzone_idx = zone_idx(zones[0]);

 restart:

       /* Go through the zonelist once, looking for a zone with enough free */

       for (i = 0; (z = zones[i]) != NULL; i++) {   //遍历zones内存管理区数组中,所有的内存管理区

              if (!zone_watermark_ok(z, order, z->pages_low,

                                   classzone_idx, 0, 0))

                     continue;

              page = buffered_rmqueue(z, order, gfp_mask);    //这里进入页框分配器,分配页框

              if (page)

                     goto got_pg;

       }

       for (i = 0; (z = zones[i]) != NULL; i++)   //上面的操作没有分配出来页框,那么唤醒内核线程kswapd。这部分内容参见第十七章

              wakeup_kswapd(z, order);

       for (i = 0; (z = zones[i]) != NULL; i++) {

              if (!zone_watermark_ok(z, order, z->pages_min,// pages_min保留页框的个数

                                   classzone_idx, can_try_harder,

                                   gfp_mask & __GFP_HIGH))

                     continue;

              page = buffered_rmqueue(z, order, gfp_mask);

              if (page)

                     goto got_pg;

       }

       //如果此次内核控制路径不是中断处理程序也不是可延迟函数,并且这个进程试图回收页框,那么这次会忽略内存不足的阈值,使用为内存不足预留的页框来进行分配

       if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE))) && !in_interrupt()) {

              /* go through the zonelist yet again, ignoring mins */

              for (i = 0; (z = zones[i]) != NULL; i++) {

                     page = buffered_rmqueue(z, order, gfp_mask);

                     if (page)

                            goto got_pg;

              }

              goto nopage;

       }

       /* Atomic allocations - we can't balance anything */

       if (!wait)

              goto nopage;

rebalance:

       cond_resched();     //如果没有分配到页框,那么这里检查是否需要调度。可能TIF_NEED_RESCHED已经被设置了,但是还没有到调度点,没有得到调度

       p->flags |= PF_MEMALLOC;    //设置PF_MEMALLOC,表示进程要进行内存回收

       reclaim_state.reclaimed_slab = 0;

       p->reclaim_state = &reclaim_state;

       did_some_progress = try_to_free_pages(zones, gfp_mask, order);   //紧急回收内存,具体内容参考第17章。注意,之前说过,设置了PF_MEMALLOC的进程,在请求页框的时候,会将内存保护区中的页框分配给这个进程。因此,这里是先设置进程的PF_MEMALLOC,然后让他回收页框

       p->reclaim_state = NULL;

       p->flags &= ~PF_MEMALLOC;

       cond_resched();

       if (likely(did_some_progress)) {     //如果紧急回收页框回收到了一些页框,那么再尝试一次页框分配

              for (i = 0; (z = zones[i]) != NULL; i++) {

                     if (!zone_watermark_ok(z, order, z->pages_min,

                                          classzone_idx, can_try_harder,

                                          gfp_mask & __GFP_HIGH))

                            continue;

                     page = buffered_rmqueue(z, order, gfp_mask);

                     if (page)

                            goto got_pg;

              }

       } else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {

              for (i = 0; (z = zones[i]) != NULL; i++) {

                     if (!zone_watermark_ok(z, order, z->pages_high,

                                          classzone_idx, 0, 0))

                            continue;

                     page = buffered_rmqueue(z, order, gfp_mask);

                     if (page)

                            goto got_pg;

              }

              out_of_memory(gfp_mask);     //杀死一个进程并释放内存,参考17章

              goto restart;

       }

       do_retry = 0;

       if (!(gfp_mask & __GFP_NORETRY)) {

              if ((order <= 3) || (gfp_mask & __GFP_REPEAT))

                     do_retry = 1;

              if (gfp_mask & __GFP_NOFAIL)

                     do_retry = 1;

       }

       if (do_retry) {

              blk_congestion_wait(WRITE, HZ/50);

              goto rebalance;

       }

nopage:

       if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {

              printk(KERN_WARNING "%s: page allocation failure."

                     " order:%d, mode:0x%x\n",

                     p->comm, order, gfp_mask);

              dump_stack();

       }

       return NULL;

got_pg:

       zone_statistics(zonelist, z);

       return page;

}

8.1.9.2:释放一组页框

       释放页框的函数如下所示:

fastcall void __free_pages(struct page *page, unsigned int order)

{

       if (!PageReserved(page) && put_page_testzero(page)) {    //检查页框是否是动态页框(PG_reserved位表示),并且页框没有被其他进程使用(count=0)

              if (order == 0)

                     free_hot_page(page);   //只有一页的话,释放到热每CPU高速缓存

              else

                     __free_pages_ok(page, order);   //通过伙伴系统释放

       }

}

8.2:内存区管理

       上面关注的都是按页框进行分配。但是,当我们请求小内存区的时候,如果按照页框进行分配,就会造成大量的浪费。

8.2.1:slab分配器

       slab分配器把对象,也就是一种数据结构,放入高速缓存。这个高速缓存就可以看做是这一类对象的专用储备。每个高速缓存都被划分成多个slab。每个slab由一个或者多个连续的页框组成,其中包含了已分配的对象(数据结构),也包含空闲的对象(数据结构)。

8.2.2:高速缓存描述符

       每个高速缓存都通过kmem_cache结构体描述。所有的高速缓存通过双链表连接在一起,生成了高速缓存链表cache_chain。高速缓存描述符kmem_cache_t的内容如下:

struct kmem_cache_s {

/* 1) per-cpu data, touched during every alloc/free */

       struct array_cache *array[NR_CPUS];

       unsigned int         batchcount;

       unsigned int         limit;

/* 2) touched by every alloc & free from the backend */

       struct kmem_list3 lists;    //用于连接这个高速缓存中的slab描述符

       unsigned int         objsize;    //高速缓存中每个对象的大小

       unsigned int       flags;      /* constant flags */

       unsigned int         num;      /* # of objs per slab */

       unsigned int         free_limit; /* upper limit of objects in the lists */

       spinlock_t             spinlock;

/* 3) cache_grow/shrink */

       /* order of pgs per slab (2^n) */

       unsigned int         gfporder;

       /* force GFP flags, e.g. GFP_DMA */

       unsigned int         gfpflags;

       size_t                    colour;           /* cache colouring range */

       unsigned int         colour_off;     /* colour offset */

       unsigned int         colour_next;  /* cache colouring */

       kmem_cache_t            *slabp_cache;

       unsigned int         slab_size;

       unsigned int         dflags;           /* dynamic flags */

/* 4) cache creation/removal */

       const char            *name;    //高速缓存名字

       struct list_head     next;    //用于连接下一个高速缓存描述符

};

       其中,lists成员较为关键,他指示了高速缓存中已经用完的自己所有空闲对象的slab链表slabs_full,用了部分,还剩有部分空闲对象的slab链表slabs_partial,完全没使用的slab链表slabs_free。他的成员如下:

struct kmem_list3 {

       struct list_head     slabs_partial;  //指向部分使用的slab描述符

       struct list_head     slabs_full; //指向完全使用的slab描述符

       struct list_head     slabs_free;//指向完全没使用的 slab描述符

       unsigned long      free_objects;

       int          free_touched;

       unsigned long      next_reap;

       struct array_cache *shared;

};

8.2.3:slab描述符

       高速缓存中的每个slab都有自己的描述符

struct slab {

       struct list_head     list;  //用于连接高速缓存描述符的kmem_list3中的链表字段

       unsigned long             colouroff;

       void               *s_mem;        /* including colour offset */

       unsigned int         inuse;            /* num of objs active in slab */

       kmem_bufctl_t             free;

};

       每个高速缓存的第一个slab的list的prev指向高速缓存描述符,next指向下一个与之相似的slab的slab的list.prev。slab描述符可以存在slab外部或者内部。

 

8.2.4:普通和专用高速缓存

       slab高速缓存分成两类,普通高速缓存和专用高速缓存。普通高速缓存包含以下两种

       1:cache_cache:高速缓存结构体kmem_cache自己作为一个对象,也需要高速缓存以及slab系统来存储他。存储存储高速缓存描述符的高速缓存是cache_cache。

static kmem_cache_t cache_cache = {

       .lists        = LIST3_INIT(cache_cache.lists),    //三个slab双向链表

       .batchcount   = 1,

       .limit              = BOOT_CPUCACHE_ENTRIES,

       .objsize   = sizeof(kmem_cache_t),      //高速缓存存储的每个成员的大小(这里也可以看到,这个高速缓存是存储高速缓存对象的高速缓存)

       .flags             = SLAB_NO_REAP,

       .spinlock = SPIN_LOCK_UNLOCKED,

       .name            = "kmem_cache",

};

       由于他是存储高速缓存描述符的高速缓存,可想而知,他也是系统中第一个高速缓存。因此,在高速缓存描述符构成的链表cache_chain中,他也是第一个元素。

static struct list_head cache_chain;

void __init kmem_cache_init(void)

{

……

       list_add(&cache_cache.next, &cache_chain);   //第一个被加入链表的元素

……

}

2:另外还有一些高速缓存,这种高速缓存包含的内存大小分别是32字节,64字节……2M。由于每种大小的高速缓存都有两个,分别适用于ISA DMA分配和常规分配。所以总共有26个高速缓存,因此也对应了26个高速缓存描述符。

       专用高速缓存是由kmem_cache_create创建的。在创建一个新的高速缓存的时候,会将新创建的高速缓存描述符加入到kmem_cache。并且还需要将高速缓存描述符加入cache_chain链表中。

       接口kmem_cache_destory用于删除一个高速缓存,并且将高速缓存描述符从cache_chain链表上删除。要注意的是,必须在删除高速缓存之前,删除他所有的slab。

       kmem_cache_create的代码实现如下:

kmem_cache_t *

kmem_cache_create (const char *name, size_t size, size_t align,  

       unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),

       void (*dtor)(void*, kmem_cache_t *, unsigned long))       //size为cache中存储的每个结构体的大小

{

       size_t left_over, slab_size, ralign;

       kmem_cache_t *cachep = NULL;

       if ((!name) ||

              in_interrupt() ||

              (size < BYTES_PER_WORD) ||

              (size > (1<<MAX_OBJ_ORDER)*PAGE_SIZE) ||

              (dtor && !ctor)) {

                     printk(KERN_ERR "%s: Early error in slab %s\n",

                                   __FUNCTION__, name);

                     BUG();

              }

       if (size & (BYTES_PER_WORD-1)) {    //对象大小向上对齐处理器字长

              size += (BYTES_PER_WORD-1);

              size &= ~(BYTES_PER_WORD-1);

       }

       if (flags & SLAB_HWCACHE_ALIGN) {    //这里是为了让cache中的每个对象都按照一定的方式对齐,这样能够保证不同的结构体对象在同一个cache_line上,加快访问效率

              ralign = cache_line_size();

              while (size <= ralign/2)

                     ralign /= 2;

       } else {

              ralign = BYTES_PER_WORD;

       }

       if (ralign < ARCH_SLAB_MINALIGN) {

              ralign = ARCH_SLAB_MINALIGN;

              if (ralign > BYTES_PER_WORD)

                     flags &= ~(SLAB_RED_ZONE|SLAB_STORE_USER);

       }

       if (ralign < align) {

              ralign = align;

              if (ralign > BYTES_PER_WORD)

                     flags &= ~(SLAB_RED_ZONE|SLAB_STORE_USER);

       }

       align = ralign;

       /* Get cache's description obj. */

       cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);

       if (!cachep)

              goto opps;

       memset(cachep, 0, sizeof(kmem_cache_t));

       /* Determine if the slab management is 'on' or 'off' slab. */

       if (size >= (PAGE_SIZE>>3))     //当每个成员的size大于一页的1/8的时候,就将slab结构体放在高速缓存空间内

              flags |= CFLGS_OFF_SLAB;

       size = ALIGN(size, align);

       if ((flags & SLAB_RECLAIM_ACCOUNT) && size <= PAGE_SIZE) {   //如果一个对象的size小于一页,那么就只需要分配1页,给这个高速缓存

              cachep->gfporder = 0;   //只分配一页

              cache_estimate(cachep->gfporder, size, align, flags,

                                   &left_over, &cachep->num);    //这个函数的作用是,计算得到cachep->num,这个成员代表的含义是,这高速缓存中可以存储多少个对象

       } else {

              do {

                     unsigned int break_flag = 0;

cal_wastage:

                     cache_estimate(cachep->gfporder, size, align, flags,

                                          &left_over, &cachep->num);

                     if (break_flag)

                            break;

                     if (cachep->gfporder >= MAX_GFP_ORDER)

                            break;

                     if (!cachep->num)

                            goto next;

                     if (flags & CFLGS_OFF_SLAB &&

                                   cachep->num > offslab_limit) {

                            /* This num of objs will cause problems. */

                            cachep->gfporder--;

                            break_flag++;

                            goto cal_wastage;

                     }

                     /*

                      * Large num of objs is good, but v. large slabs are

                      * currently bad for the gfp()s.

                      */

                     if (cachep->gfporder >= slab_break_gfp_order)

                            break;

                     if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder))

                            break;     /* Acceptable internal fragmentation. */

next:

                     cachep->gfporder++;

              } while (1);

       }

       if (!cachep->num) {

              printk("kmem_cache_create: couldn't create cache %s.\n", name);

              kmem_cache_free(&cache_cache, cachep);

              cachep = NULL;

              goto opps;

       }

       slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t)

                            + sizeof(struct slab), align);    //这里获得slab的尺寸,可以看到,包含了结构体struct slab,还包含了num个kmem_bufctl_t

       if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {

              flags &= ~CFLGS_OFF_SLAB;

              left_over -= slab_size;

       }

       if (flags & CFLGS_OFF_SLAB) {

              slab_size = cachep->num*sizeof(kmem_bufctl_t)+sizeof(struct slab);

       }

       cachep->colour_off = cache_line_size();

       if (cachep->colour_off < align)

              cachep->colour_off = align;

       cachep->colour = left_over/cachep->colour_off;

       cachep->slab_size = slab_size;

       cachep->flags = flags;

       cachep->gfpflags = 0;

       if (flags & SLAB_CACHE_DMA)

              cachep->gfpflags |= GFP_DMA;

       spin_lock_init(&cachep->spinlock);

       cachep->objsize = size;

       /* NUMA */

       INIT_LIST_HEAD(&cachep->lists.slabs_full);

       INIT_LIST_HEAD(&cachep->lists.slabs_partial);

       INIT_LIST_HEAD(&cachep->lists.slabs_free);    //初始化cache的三个链表

       if (flags & CFLGS_OFF_SLAB)

              cachep->slabp_cache = kmem_find_general_cachep(slab_size,0);

       cachep->ctor = ctor;

       cachep->dtor = dtor;

       cachep->name = name;

       /* Don't let CPUs to come and go */

       lock_cpu_hotplug();

       if (g_cpucache_up == FULL) {

              enable_cpucache(cachep);

       } else {

              if (g_cpucache_up == NONE) {

                     cachep->array[smp_processor_id()] = &initarray_generic.cache;

                     g_cpucache_up = PARTIAL;

              } else {

                     cachep->array[smp_processor_id()] = kmalloc(sizeof(struct arraycache_init),GFP_KERNEL);

              }

              BUG_ON(!ac_data(cachep));

              ac_data(cachep)->avail = 0;

              ac_data(cachep)->limit = BOOT_CPUCACHE_ENTRIES;

              ac_data(cachep)->batchcount = 1;

              ac_data(cachep)->touched = 0;

              cachep->batchcount = 1;

              cachep->limit = BOOT_CPUCACHE_ENTRIES;

              cachep->free_limit = (1+num_online_cpus())*cachep->batchcount

                                   + cachep->num;

       }

       cachep->lists.next_reap = jiffies + REAPTIMEOUT_LIST3 +

                                   ((unsigned long)cachep)%REAPTIMEOUT_LIST3;

       /* Need the semaphore to access the chain. */

       down(&cache_chain_sem);

       {

              struct list_head *p;

              mm_segment_t old_fs;

              old_fs = get_fs();

              set_fs(KERNEL_DS);

              list_for_each(p, &cache_chain) {

                     kmem_cache_t *pc = list_entry(p, kmem_cache_t, next);

                     char tmp;

                     if (__get_user(tmp,pc->name)) {

                            printk("SLAB: cache with size %d has lost its name\n",

                                   pc->objsize);

                            continue;

                     }    

                     if (!strcmp(pc->name,name)) {

                            printk("kmem_cache_create: duplicate cache %s\n",name);

                            up(&cache_chain_sem);

                            unlock_cpu_hotplug();

                            BUG();

                     }     

              }

              set_fs(old_fs);

       }

       /* cache setup completed, link it into the list */

       list_add(&cachep->next, &cache_chain);

       up(&cache_chain_sem);

       unlock_cpu_hotplug();

opps:

       if (!cachep && (flags & SLAB_PANIC))

              panic("kmem_cache_create(): failed to create slab `%s'\n",

                     name);

       return cachep;

}

       这里单独说明一下函数

static void cache_estimate (unsigned long gfporder, size_t size, size_t align,

               int flags, size_t *left_over, unsigned int *num)

{

       int i;

       size_t wastage = PAGE_SIZE<<gfporder;    //这个是高速缓存cache的order对应的size,单位为字节。每次对cache进行扩容的时候,都是再给他分配1<<order个页。

       size_t extra = 0;

       size_t base = 0;

       if (!(flags & CFLGS_OFF_SLAB)) {

              base = sizeof(struct slab);

              extra = sizeof(kmem_bufctl_t);

       }

       i = 0;

       while (i*size + ALIGN(base+i*extra, align) <= wastage)

              i++;      //这里是计算出这么大的size总共可以存下多少个对象。这里也要注意,每一个对象都对应的一个kmem_bufctl_t结构体

       if (i > 0)

              i--;

       if (i > SLAB_LIMIT)

              i = SLAB_LIMIT;

       *num = i;

       wastage -= i*size;

       wastage -= ALIGN(base+i*extra, align);

       *left_over = wastage;

}

8.2.5:给高速缓存分配slab,以及删除一个slab

       一个新创建的高速缓存,不包含任何slab。只有当,向一个高速缓存发出一个分配新对象的请求,并且高速缓存不包含任何空闲对象的时候,才会给高速缓存分配slab。

static int cache_grow (kmem_cache_t * cachep, int flags, int nodeid)

       使用此函数向高速缓存中分配一个slab。

       首先进入kmem_getpages为slab分配页框。

static int cache_grow (kmem_cache_t * cachep, int flags, int nodeid)

{

       struct slab      *slabp;

       void        *objp;

       size_t             offset;

       int          local_flags;

       unsigned long      ctor_flags;

       if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW))

              BUG();

       if (flags & SLAB_NO_GROW)

              return 0;

       ctor_flags = SLAB_CTOR_CONSTRUCTOR;

       local_flags = (flags & SLAB_LEVEL_MASK);

       if (!(local_flags & __GFP_WAIT))

              ctor_flags |= SLAB_CTOR_ATOMIC;

       /* About to mess with non-constant members - lock. */

       check_irq_off();

       spin_lock(&cachep->spinlock);

       /* Get colour for the slab, and cal the next value. */

       offset = cachep->colour_next;

       cachep->colour_next++;

       if (cachep->colour_next >= cachep->colour)

              cachep->colour_next = 0;

       offset *= cachep->colour_off;

       spin_unlock(&cachep->spinlock);

       if (local_flags & __GFP_WAIT)

              local_irq_enable();

       kmem_flagcheck(cachep, flags);

       /* Get mem for the objs. */

       if (!(objp = kmem_getpages(cachep, flags, nodeid)))//此处通过伙伴系统完成页框分配,返回分配的页框块的第一个页框的线性地址。具体分析见下一小节

              goto failed;

       /* Get slab management. */

       if (!(slabp = alloc_slabmgmt(cachep, objp, offset, local_flags)))    //在动态内存中分配slabp结构体。这里会根据cachep的属性,判断是在另一个专门存放slab描述符的高速缓存中分配,还是就在slabp的第一个页框中分配。

              goto opps1;

       set_slab_attr(cachep, slabp, objp);   //刚刚分配了以objp为首的1<<order个页框。这个函数将这些页框的页框描述符中的lru字段置位

page->lru.next = cachep;

page->lru.prev = objp;

       cache_init_objs(cachep, slabp, ctor_flags);

       if (local_flags & __GFP_WAIT)

              local_irq_disable();

       check_irq_off();

       spin_lock(&cachep->spinlock);

       /* Make slab active. */

       list_add_tail(&slabp->list, &(list3_data(cachep)->slabs_free));   //将新分配的slab加入到高速缓存cachep的slabs_free链表中

       list3_data(cachep)->free_objects += cachep->num;

       spin_unlock(&cachep->spinlock);

       return 1;

opps1:

       kmem_freepages(cachep, objp);

failed:

       if (local_flags & __GFP_WAIT)

              local_irq_disable();

       return 0;

}

       使用函数slab_destroy删除一个slab。

8.2.6:slab分配器与分区页框分配器的接口

       刚刚已经介绍了,如何给一个高速缓存中创建新的slab。这里介绍如何创建一个新的slab。创建新的slab的时候,必须使用页框分配器(也就是之前所说的,先通过内存区管理区确定内存管理区,再在此管理区中分配页框的函数)来获得一组连续的空闲页框。使用函数kmem_getpages

static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)

{

       struct page *page;

       void *addr;

       int i;

       flags |= cachep->gfpflags;

       if (likely(nodeid == -1)) {

              page = alloc_pages(flags, cachep->gfporder);    //这里就是前面讲的页框分配器,通过伙伴系统,获得空闲页框

       } else {

              page = alloc_pages_node(nodeid, flags, cachep->gfporder);

       }

       if (!page)

              return NULL;

       addr = page_address(page);    //如果不是高端内存,那么就返回页框对应的虚拟地址;如果是高端内存,就返回页框的页框描述符的虚拟地址

       i = (1 << cachep->gfporder);

       if (cachep->flags & SLAB_RECLAIM_ACCOUNT)

              atomic_add(i, &slab_reclaim_pages);

       add_page_state(nr_slab, i);

       while (i--) {          //将所有分配的页的PG_slab置位

              SetPageSlab(page);

              page++;

       }

       return addr;

}

完成页框分配。它会返回分配的页框的线性地址。

       使用函数kmem_freepages,将slab的页框返还给伙伴系统

static void kmem_freepages(kmem_cache_t *cachep, void *addr)

{

       unsigned long i = (1<<cachep->gfporder);

       struct page *page = virt_to_page(addr);

       const unsigned long nr_freed = i;

       while (i--) {

              if (!TestClearPageSlab(page))

                     BUG();

              page++;

       }

       sub_page_state(nr_slab, nr_freed);

       if (current->reclaim_state)

              current->reclaim_state->reclaimed_slab += nr_freed;

       free_pages((unsigned long)addr, cachep->gfporder);

       if (cachep->flags & SLAB_RECLAIM_ACCOUNT)

              atomic_sub(1<<cachep->gfporder, &slab_reclaim_pages);

}

完成slab的页框释放。

8.2.7:分配slab对象

       通过函数kmem_cache_alloc,在高速缓存中分配一个新对象。

void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)

{

       return __cache_alloc(cachep, flags);

}

static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)

{

       unsigned long save_flags;

       void* objp;

       struct array_cache *ac;

       local_irq_save(save_flags);

       ac = ac_data(cachep);

       if (likely(ac->avail)) {

              ac->touched = 1;

              objp = ac_entry(ac)[--ac->avail];   //objp=((void **)(ac+1))[--ac->avail],得到一个对象的地址

       } else {

              objp = cache_alloc_refill(cachep, flags);

       }

       local_irq_restore(save_flags);

       return objp;

}

8.2.8:对象描述符

       存放在slab中的每个对象都有一个对象描述符

typedef unsigned short kmem_bufctl_t;

从创建一个高速缓存的时候也知道,每个slab描述符后面就是跟着num个kmem_bufctl_t,num就是一个slab中包含的对象的个数。因此,kmem_bufctl_t和结构体是一一对应的关系。kmem_bufctl_t包含的是下一个空闲对象在slab中的下标。

8.2.9:slab着色

       着色主要用于处理下面的问题:由于不同的内存块可能映射到相同的缓存行之上,在同一个cache的不同slab内,有相同偏移量的对象很可能映射到同一高速缓存行中。这样就可能发生cache的抖动。

       之前讲过,一个高速缓存中一般有没有用完的字节。使用这些字节,让同一cache中不同的slab的对象结构体存放首地址不同。

       例如,从下图中看,就是不同的颜色col,导致每个对象的位置不同。

 8.2.10:空闲slab对象的本地缓存

       高速缓存结构体中的成员

struct array_cache *array[NR_CPUS];

有着重要的含义:

struct array_cache {

       unsigned int avail;

       unsigned int limit;

       unsigned int batchcount;

       unsigned int touched;

};

array本质上是一个二级数组。第一级是NR_CPUS个struct array_cache *结构体。因此,第二级是一个指针。在初始化的时候,struct array_cache *分配的内存大小并不是struct array_cache的大小,而是

struct arraycache_init {

       struct array_cache cache;

       void * entries[BOOT_CPUCACHE_ENTRIES];

};

cachep->array[smp_processor_id()] = kmalloc(sizeof(struct arraycache_init),GFP_KERNEL);

的大小。entries同样是一个数组,每个数组成员都是一个地址,这个地址就是空闲的对象结构体的首地址。因此,在代码中,我们会经常看到,使用

ac_entry(ac)[--ac->avail];

来分配一个结构体。

通过函数kmem_cache_alloc()获得新对象,代码如下所示:

void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)

{

       return __cache_alloc(cachep, flags);

}

static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)

{

       unsigned long save_flags;

       void* objp;

       struct array_cache *ac;

       local_irq_save(save_flags);

       ac = ac_data(cachep);

       if (likely(ac->avail)) {    //要记得,在我们第一次创建cache的时候,我们将avail这个成员设置为0。

              ac->touched = 1;

              objp = ac_entry(ac)[--ac->avail];

       } else {

              objp = cache_alloc_refill(cachep, flags);

       }

       local_irq_restore(save_flags);

       return objp;

}

       因此,在第一次malloc的时候,主要要看看函数cache_alloc_refill做了些什么

static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)

{

       int batchcount;

       struct kmem_list3 *l3;

       struct array_cache *ac;

       check_irq_off();

       ac = ac_data(cachep);

retry:

       batchcount = ac->batchcount;    // batchcount:本地高速缓存重新填充或者腾空时使用的块大小

       if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {   //如果这个高速缓存最近被使用过,那么touched标志被设置为1

              batchcount = BATCHREFILL_LIMIT;

       }

       l3 = list3_data(cachep);

       spin_lock(&cachep->spinlock);

       if (l3->shared) {   //这里我们假设shared为0

              struct array_cache *shared_array = l3->shared;

              if (shared_array->avail) {

                     if (batchcount > shared_array->avail)

                            batchcount = shared_array->avail;

                     shared_array->avail -= batchcount;

                     ac->avail = batchcount;

                     memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail],

                                   sizeof(void*)*batchcount);

                     shared_array->touched = 1;

                     goto alloc_done;

              }

       }

       while (batchcount > 0) {

              struct list_head *entry;

              struct slab *slabp;

              entry = l3->slabs_partial.next;   //这里是拿到部分使用的slab

              if (entry == &l3->slabs_partial) {    //链表操作,如果已经遍历了一遍,但是还是没有找到合适的slab的话,就要进入新建一个slab的流程

                     l3->free_touched = 1;

                     entry = l3->slabs_free.next;

                     if (entry == &l3->slabs_free)

                            goto must_grow;

              }

              slabp = list_entry(entry, struct slab, list);    //这里找到了一个slab

              check_slabp(cachep, slabp);

              check_spinlock_acquired(cachep);

              while (slabp->inuse < cachep->num && batchcount--) {

                     kmem_bufctl_t next;

                     ac_entry(ac)[ac->avail++]=slabp->s_mem+ slabp->free * cachep->objsize;    

// s_mem:slab中第一个对象的地址,因此,ac_entry(ac)[ac->avail++]就是一个空闲对象的地址

                     slabp->inuse++;

                     next = slab_bufctl(slabp)[slabp->free];

                       slabp->free = next;    //free:slab中下一个空闲对象的下标

              }

              check_slabp(cachep, slabp);

              /* move slabp to correct slabp list: */

              list_del(&slabp->list);

              if (slabp->free == BUFCTL_END)

                     list_add(&slabp->list, &l3->slabs_full);

              else

                     list_add(&slabp->list, &l3->slabs_partial);

       }

must_grow:

       l3->free_objects -= ac->avail;

alloc_done:

       spin_unlock(&cachep->spinlock);

       if (unlikely(!ac->avail)) {

              int x;

              x = cache_grow(cachep, flags, -1);

             

              // cache_grow can reenable interrupts, then ac could change.

              ac = ac_data(cachep);

              if (!x && ac->avail == 0)     // no objects in sight? abort

                     return NULL;

              if (!ac->avail)        // objects refilled by interrupt?

                     goto retry;

       }

       ac->touched = 1;

       return ac_entry(ac)[--ac->avail];

}

8.2.11:释放slab对象

       释放slab对象的代码如下:

void kmem_cache_free (kmem_cache_t *cachep, void *objp)

{

       unsigned long flags;

       local_irq_save(flags);

       __cache_free(cachep, objp);

       local_irq_restore(flags);

}

static inline void __cache_free (kmem_cache_t *cachep, void* objp)

{

       struct array_cache *ac = ac_data(cachep);

       check_irq_off();

       if (likely(ac->avail < ac->limit)) {

              ac_entry(ac)[ac->avail++] = objp;

              return;

       } else {

              cache_flusharray(cachep, ac);

              ac_entry(ac)[ac->avail++] = objp;

       }

}

       如果本地高速缓存中,没有位置来存放空闲对象的指针,就进入函数

static void cache_flusharray (kmem_cache_t* cachep, struct array_cache *ac)

{

       int batchcount;

       batchcount = ac->batchcount;

       check_irq_off();

       spin_lock(&cachep->spinlock);

       if (cachep->lists.shared) {     //先不考虑这种情况

              struct array_cache *shared_array = cachep->lists.shared;

              int max = shared_array->limit-shared_array->avail;

              if (max) {

                     if (batchcount > max)

                            batchcount = max;

                     memcpy(&ac_entry(shared_array)[shared_array->avail],

                                   &ac_entry(ac)[0],

                                   sizeof(void*)*batchcount);

                     shared_array->avail += batchcount;

                     goto free_done;

              }

       }

       free_block(cachep, &ac_entry(ac)[0], batchcount);

free_done:

       spin_unlock(&cachep->spinlock);

       ac->avail -= batchcount;

       memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount],

                     sizeof(void*)*ac->avail);

}

       上面函数的关键操作都在free_block中完成;

static void free_block(kmem_cache_t *cachep, void **objpp, int nr_objects)

{

       int i;

       check_spinlock_acquired(cachep);

       cachep->lists.free_objects += nr_objects;

       for (i = 0; i < nr_objects; i++) {

              void *objp = objpp[i];

              struct slab *slabp;

              unsigned int objnr;

              slabp = GET_PAGE_SLAB(virt_to_page(objp));    //slab包含的页框的页描述符的lru.prev指向相应的slab描述符

              list_del(&slabp->list);

              objnr = (objp - slabp->s_mem) / cachep->objsize;

              check_slabp(cachep, slabp);

              slab_bufctl(slabp)[objnr] = slabp->free; 

              slabp->free = objnr;        //在分配对象的时候会用到free字段。下次分配的时候,会优先分配这次释放的对象占据的内存空间

              slabp->inuse--;

              check_slabp(cachep, slabp);

              /* fixup slab chains */

              if (slabp->inuse == 0) {    //如果slab没有一个对象被使用

                     if (cachep->lists.free_objects > cachep->free_limit) {

                            cachep->lists.free_objects -= cachep->num;

                            slab_destroy(cachep, slabp);      //这种情况下删除slab

                     } else {

                            list_add(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_free);

                     }

              } else {

                     list_add_tail(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_partial); 

 //将slab加入部分使用,部分为使用的链表中

              }

       }

}

8.2.12:通用对象

       如果是分配一些普通的结构体,就使用普通高速缓存来处理。

static inline void *kmalloc(size_t size, int flags)

{

       return __kmalloc(size, flags);

}

void * __kmalloc (size_t size, int flags)

{

       struct cache_sizes *csizep = malloc_sizes;

       for (; csizep->cs_size; csizep++) {

              if (size > csizep->cs_size)

                     continue;

              return __cache_alloc(flags & GFP_DMA ?

                      csizep->cs_dmacachep : csizep->cs_cachep, flags);   //还是调用函数__cache_alloc进行内存分配,但是不同的是,这时候指定的高速缓存是cs_dmacachep或者cs_cachep

       }

       return NULL;

}

8.3:非连续内存区管理

       将内存区映射到一组连续的页框是最好的选择,这样能够充分利用高速缓存并获得较低的访问时间。但是,如果对内存区的请求不是很频繁。那么,通过连续的线性地址来访问非连续的页框也会很有意义。这样的主要优点是避免外碎片,缺点是要打乱内核页表。

8.3.1:非连续内存区的线性地址

        这张图可以看到内核的线性地址空间分配。物理内存映射部分是对0~896M内存进行映射的线性地址。FIXADDR_START~4G是包含固定映射的线性地址(固定映射的线性地址),PKMAP_BASE开始是用于高端内存页框的永久内核映射的线性地址(高端内存页框的内核映射)。

       最终,剩下的,从VMALLOC_START开始,到VMALLOC_END结束的线性地址用于非连续内存区映射。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值