八、内存管理(三)

3、非连续内存区管理

如果对内存区的请求不是很频繁,那么,通过连续的线性地址来访问非连续的页框这样一种分配模式就很有意义。这种模式的主要优点是避免了外碎片,而缺点是必须打乱内核页表。非连续内存区的大小必须是4096的倍数。linux在几个方面使用非连续内存区,例如,为活动的交换区分配数据结构,为模块分配空间,或给某些IO驱动程序分配缓冲区。此外,非连续内存区还提供了另一种使用高端内存页框的方法。

3.1 分连续内存区的线性地址

要查找一个线性地址的空闲区,从PAGE_OFFSET开始查找,即第4个GB的其实地址

1、内存区的开始部分包含的是是对前896MB RAM进行映射的线性地址;直接映射的物理内存末尾多对应的线性地址保存在high_memory变量中。

2、内存区的结尾部分包含的是固定映射线性地址。

3、从PKMAP_BASE开始,我们查找用于高端内存页框的永久内核映射的线性地址

4、其余的线性地址可以用于非连续内存区。在物理内存映射的末尾与第一个内存区之间插入一个大小为8MB的安全区,目的是为了捕获对内存的越界访问。处于同样的理由,插入其他4KB大小的安全区来隔离非连续内存区。为非连续内存区保留的线性地址空间的起始地址由VMALLOC_START宏定义,而末尾由VMALLOC_END宏定义

3.2 非连续内存区描述符

struct vm_struct{

  void * addr;//内存区第一个内存单元的地址

  unsigned long size; //内存区的大小加上4k隔离区的值

  unsigned long flags; //非连续内存区映射的内存的类型

  struct page ** pages; //非连续内存区包含的页描述符数组

  unsigned int nr_pages;//页数量

  unsigned long phys_addr;//该字段设为0,除非内存已被创建来映射一个硬件设备的IO共享内存

  struct vm_struct * next;//指向下一个vm_struct结构的指针

}

flags字段标识了非连续区映射内存的类型:VM_ALLOC表示使用vmalloc()得到的页,VM_MAP表示使用vmap()映射已经分配的页,VM_IOREMAP表示ioremap()映射的硬件设备的板上内存。

get_vm_area()函数在线性地址VMALLOC_START和VMALLOC_END之间查找一个空闲区域。函数使用size和flag两个参数,步骤如下:

1、调用kmalloc()为vm_struct类型的新描述符获得一个内存区

2、得到vmlist_lock锁,并扫描类型为vm_struct的描述符链表来查找线性地址的一个空闲区域,至少覆盖size+4096个地址。

3、如果存在这样一个区间,函数就初始化描述符字段,释放vmlist_lock锁,返回非连续内存区描述符的起始地址。

4、否则释放描述符,释放锁,返回NULL

3.3 分配非连续内存区

vmalloc()函数给内核分配一个非连续内存区,参数size表示所请求的内存区大小

void *vmalloc(unsigned long size){

  struct vm_struct *area;

  struct page **pages;

  unsigned int array_size,i;

  size = (size +PAGE_SIZE -1)&PAGE_MASK;

  area = get_vm_area(size,VM_ALLOC);

  if(!area)

    return NULL;

  area->nr_pages =size>>PAGE_SHIFT;

  array_size =  (area->nr_pages *sizeof(struct page *));

  area->pages = pages = kmalloc(array_size,GFP_KERNEL);

  if(!area->pages){

    remove_vm_area(area->addr);

    kfree(area);

    return NULL;

  }

  memset(area->pages,0,array_size);

  for(i=0;i<area->nr_pages;i++){
    area->pages[i] = alloc_page(GFP_KERNEL|__GFP_HIGHMEM);

    if(!area->pages[i]){

      area->nr_pages = i;

    fail:vfree(area->addr);

      return NULL:

    }

  }

  if(map_vm_area(area,__pgprot(0x63),&pages));

    goto fail;

  return area->addr;

}

map_vm_area(struct vm_struct * area,prot,struct page ** pages){

  unsigned long address,end;

  address = area->addr;

  end = address+(area->size-PAGE_SIZE);

  pgd = pgd_offset_k(address);//主内核页全局目录中的目录项,该项对应于内存区其实线性地址

  spin_lock(&init_mm.page_table_lock);//获得内核页表自旋锁

  int ret = 0;

  for(i=pgd_index(address);i<pgd_index(end-1);i++){

    pud_t *pud = pud_alloc(&init_mm,pgd,address);//为新的内存区创建一个页上级目录,并把它的物理地址写入内核全局目录的合适表项。然后调用alloc_area_pud()为新的页上级目录分配所有相关的页表。

    ret = -ENOMEM;

    if(!pud) break;

    next = (address + PGDIR_SIZE)&PGDIR_MASK;

    if(next<address||next>end)

      next = end;

    if(map_area_pud(pud,address,next,prot,pages))

      break;

    address = next;

    pgd++;

    ret = 0;

}

map_area_pud():

  do{

    pmd_t *pmd = pmd_alloc(&init_mm,pud,address);

    if(!pmd)

      return -ENOMEM;

    if(map_area_pmd(pmd,address,end-address,prot,pages)){

      return -ENOMEM;

    address = (address + PUD_SIZE) & PUD_MASK;

    pud ++;

  }while(address<end);

 

map_area_pmd():

  do{

    pte_t *pte = pte_alloc_kernel(&init_mm,pmd,address);

    if(!pte)

      return -ENOMEM;

    if(map_area_pte(pte,address,end-address,prot,pages))

      return -ENOMEM;

    address = (address + PMD_SIZE)&PMD_MASK;

    pmd ++;

  }while(address<end);

 

map_area_pte():

   do{

     struct page *page = **pages;

     set_pte(pte,mk_pte(page,prot);

     address += PAGE_SIZE;

     pte++; 

     (*pages)++;

  }while(address<end);

3.4 释放非连续内存区

vfree-----vmalloc()、vmalloc32();

vunmap----vmap();

vfree和vunmap都依赖于__vunmap()函数。

__vunmap()接收两个参数,将要释放内存区的起始地址addr,以及标致deallocate_pages,如果被映射到内存区内的页框应当被释放到页框分配器,这个标志设置;步骤:

1、调用remove_vm_area()函数得到vm_struct描述符的地址area,并清除内存区中线性地址对应的内核页表项。

2、如果deallocate_pages被设置,函数扫描指向页描述符的area->pages指针数组;对于数组的每一个元素,调用__free_page()函数释放页框到分区分配器。此外,执行kfree(area->pages)来释放数组本身。

3、调用kfree(area)来释放vm_struct描述符

remove_vm_area():

 write_lock(&vmlist_lock);

 for(p=&vmlist;(tmp=*p);p=&tmp->next){

    if(tmp->addr == addr){

      unmap_vm_area(tmp);

      *p = tmp->next;

      break;

    }

  }

 write_unlock(&vmlist_lock);

 return tmp;

 

unmap_vm_area():

  address = area->addr;

  end = address +area->size;

  pgd = pgd_offset_k(address);

  for(i=pgd_index(address);i<=pgd_index(end);i++){

    next = (address+PGDIR_SIZE)&PGDIR_MASK;

    if(next <=address || next >end);

      next = end;

    unmap_area_pud(pgd,address,next-address);

    address = next;

    pgd++;

  }

 

unmap_area_pud():

  do{

    unmap_area_pmd(pud,address,end-address)

    address = (address +PUD_SIZE)&PUD_MASK;

    pud++;

  }while(address&&(address<end));

 

unmap_area_pmd():

  do{

    unmap_area_pte(pmd,address,end-address);

    address = (address + PMD_SIZE)&PMD_MASK;

    pmd++;

  }while(address<end);

 

unmap_area_pte():

  do{

    pte_t page = ptep_get_and_clear(pte);

    address += PAGE_SIZE;

    pte++;

    if(!pte_none(page)&&!pte_present(page))

      printk("Whee..Swapped out page in kermel page table\n");

  }while(address<end);

 

    

posted @ 2013-04-24 00:34  shuying1234  阅读(276)  评论(0编辑  收藏  举报