Linux内存管理--高端内存映射与非连续内存分配
- 2013-10-14 09:16:15 | 新闻来源:叶凡网络 | 点击量:923
该函数修改的主内核页全局目录和它页表表项,与vmalloc一样。内核永远不会回收页全局,页上级,页中间目录,也不会回收页表,而进程的页表会指向这些表项。这样的话,假设一个内核进程访问已经释放的非连续内存,最终就会访问到已经被清空的页表表项,从而引发缺页异常,这就是一个错误。
高于896物理内存在内核中属于高端内存,并没有对内存做一一的映射,系统保存了128M线性地址空间来临时映射这些高于896M高端物理内存,该线性地址为3G+768m~4G返回页框线性地址的页分配函数对于高端内存是无效的因为高端内存不会自动的映射到某个线性地址。例如__get_free_pagGFP_HIGH_MEM,对于32位的机器来说。0函数分配高端内存页框时,返回的NULL内核可以采用三种方式来使用高端物理内存:永久内核映射,临时内核映射和非连续内存分配。建立永久内核映射可能会阻塞当前进程的执行,这发生在没有高端内存没有空闲的页表项来做映射的情况下,因此在中断等不能阻塞的代码中不要使用永久内核映射。临时内核映射不会发生阻塞的情况,但必需保证没有其他内核路径在使用同样的临时内核映射。
一、永久内存映射
其地址存放在pkmap_page_t中,永久内核映射使用的内核主页表中的一个专门的页表。页表的页表项由宏LA ST_PKMA P发生,页表中包含512或者1024项。
pkmap_count数组包含了LA ST_PKMA P个计数器,该页表映射的线性地址从PKMA P_BA SE开始。pkmap_page_t页表中的每项都有对应一个计数值:
计数器为0对应的页表项是空闲可用的
但是不能够使用,计数器为1对应的页表项没有映射任何高端内存。因为自从最后一次使用以来,其相应的TLB尚未被刷新。
计数器为n有多个内核成分使用该页表项所对应的页框。
源码分析:
void fastcall *kmap_highstruct page *pag
- { unsigned long vaddr;
- spin_lock&kmap_lock; //page->virtu记录了页框对应的线性地址
- vaddr = unsigned longpage_addresspage; //若页框未被映射过,分配新的空闲页表项
- if !vaddr vaddr = map_new_virtupage;
- //若是刚分配到空闲页表项的话,map_new_virtu中其count //值被设置为了1这里再次++
- pkmap_count[PKMA P_NRvaddr]++; BUG_ONpkmap_count[PKMA P_NRvaddr] < 2;
- spin_unlock&kmap_lock; return void* vaddr;
- } static inline unsigned long map_new_virtustruct page *pag
- { unsigned long vaddr;
- int count;
- start:
- count = LA ST_PKMA P; //寻找一个空的页表项
- for ;; { //从上一次找到空闲页表项的位置开始寻找
- last_pkmap_nr = last_pkmap_nr + 1 & LA ST_PKMA P_MA SK; if !last_pkmap_nr {
- flush_all_zero_pkmap; count = LA ST_PKMA P;
- } //找到一个未用的空闲页表项
- if !pkmap_count[last_pkmap_nr] break; /* Found a usable entry */
- //count变为0话,意味着当前没有空闲的页表项 if --count
- continue; //没有找到空闲的页表项,将当前进程加入到等待队列,进行调度,直到
- //有空闲的页表项或者该页面被别人映射 {
- DECLA RE_WA ITQUEUEwait, curr;
- __set_current_stTA SK_UNINTERRUPTIBLE;
- add_wait_queu&pkmap_map_wait, &wait; spin_unlock&kmap_lock;
- schedul; remove_wait_queu&pkmap_map_wait, &wait;
- spin_lock&kmap_lock; //有可能在该进程睡眠期间,有其它进程对该页面做了内存映射
- if page_addresspage return unsigned longpage_addresspage;
- /* Re-start */ goto start;
- } }
- //得到对应页表项对应的线性地址 vaddr = PKMA P_A DDRlast_pkmap_nr;
- //设置对应的页表项 set_pte_at&init_mm, vaddr,
- &pkmap_page_table[last_pkmap_nr], mk_ptepage, kmap_prot; //设置永久内存映射数组的值
- pkmap_count[last_pkmap_nr] = 1; //将page->virtu值设为vaddr,ok
- set_page_addresspage, void *vaddr;
- return vaddr;
- }
二、临时内核映射
内核中,临时内核映射比较简单。为每个cpu都保存了一组页表项,每个页表项由一个特定的内核成分使用,需要注意的不同的内核控制路径不应该同时使用一个页表项,这样的话,会使后一个内核控制路径将前一个内核控制路径设置页表项给冲掉。
建立临时内核映射使用kmap_atom函数。
void *__kmap_atomstruct page *page, enum km_type typ- { enum fixed_addresses idx;
- unsigned long vaddr;
- //禁止内核抢占,以预防不同内核控制路径使用同一页表项
- inc_preempt_count; //非高端内存,不用进行高端内存映射
- if !PageHighMempage return page_addresspage;
- //得到使用的页表项的下表索引 idx = type + KM_TYPE_NR*smp_processor_id;
- //得到相关页表项的线性地址 vaddr = __fix_to_virtFIX_KMA P_BEGIN + idx;
- //设置对应的页表项 set_ptkmap_pte-idx, mk_ptpage, kmap_prot;
- local_flush_tlb_onunsigned longvaddr;
- return void* vaddr;
- }
三、非连续内存分配
下图显示了如何使用高于0xc0000000线性地址的线性地址空间:
- 内存区的开始局部包括的对前896MBRA M进行映射的线性地址,直接映射的物理内存的末尾的线性地址保管在high_memori变量中。
- 内存区的结尾位置包括的固定映射的线性地址。
- 从PKMA P_BA SE开始,用于高端内存永久映射的线性地址。
- 其余的线性地址用于非连续内存区,物理内存映射和第一个内存区间有一个8M平安区,用于捕捉对内存的越界访问,同样道理,插入其它4KB大小的内存区来隔离非连续内存区。
非连续内存区描述符数据结构:
struct vm_struct {- void *addr;//内存区第一个内存单元的线性地址 unsigned long size;//内存区的大小加上4K4K用来检查越界的内存
- unsigned long flags;//非连续内存的类型,VM_A LLOC表示使用vmalloc分配的内存,VM_MA P表示使用vmap分配的内存, //VM_IOREMA P表示用ioremap分配的内存
- struct page **pages;//非连续内存的物理页数组 unsigned int nr_pages;//非连续内存的物理页的个数
- unsigned long phys_addr; struct vm_struct *next;//用来将各个非连续内存描述符串联起来
- };
1分配非连续的内存区
vmapvmalloc会去调用__vmalloc_nod函数:分配函数主要是vmalloc.
void *__vmalloc_nodunsigned long size, gfp_t gfp_mask, pgprot_t prot,- int nod {
- struct vm_struct *area; //s要对其为4K整数倍,因为非连续内存区域是将各个物理页进行映射
- size = PA GE_A LIGNsize; if !size || size >> PA GE_SHIFT > num_physpag
- return NULL; //找到一块空闲的线性地址区域,用来映射该非连续内存
- area = get_vm_area_nodsize, VM_A LLOC, nod; if !area
- return NULL;
- return __vmalloc_area_nodarea, gfp_mask, prot, nod;
- }
- void *__vmalloc_area_nodstruct vm_struct *area, gfp_t gfp_mask,
- pgprot_t prot, int nod {
- struct page **pages; unsigned int nr_pages, array_size, i;
- //计算要映射的物理页数 nr_pages = area->size - PA GE_SIZE >> PA GE_SHIFT;
- //计算vm_struct中page数组的数组元素个数 array_size = nr_pages * sizeofstruct page *;
- //记录下物理页面的数目 area->nr_pages = nr_pages;
- //为vm_struct中的page数组分配内存 if array_size > PA GE_SIZE {
- pages = __vmalloc_nodarray_size, gfp_mask, PA GE_KERNEL, nod; area->flags |= VM_VPA GES;
- } else pages = kmalloc_nodarray_size, gfp_mask & ~__GFP_HIGHMEM, node;
- area->pages = pages; if !area->pag {
- remove_vm_areaarea->addr; kfrearea;
- return NULL; }
- memsetarea->pages, 0, array_s; //为非连续内存进行页面的分配,每次分配一个页面,将其页框指针记录在page数组中
- for i = 0; i < area->nr_pages; i++ { if node < 0
- area->pages[i] = alloc_paggfp_mask; else
- area->pages[i] = alloc_pages_nodnode, gfp_mask, 0; if unlik!area->pages[i] {
- /* Successfully allocated i pages, free them in __vunmap */ area->nr_pages = i;
- goto fail; }
- } //将各个物理页框映射到分配好的空闲线性区里面去
- if map_vm_areaarea, prot, &pag goto fail;
- return area->addr;
- fail:
- vfrearea->addr; return NULL;
- }
因此当内核态进程访问非连续内存区时,__vmalloc_nod并不触及当前进程的页表。会发生缺页异常,因为对应的进程的相应地址对应的页表项为空。当缺页异常发生时,异常处置顺序会到内核主页表(init_mm.pgd页全局目录)中去查看是否有对应的页表项,有的话,就会修改当前进程的页表项,并继续进程的执行。
2释放非连续的内存区
void vfrevoid *addr- { BUG_ONin_interrupt;
- __vunmapaddr, 1; }
- void __vunmapvoid *addr, int deallocate_pag {
- struct vm_struct *area;
- if !addr
- return; //释放的地址应该是4k整数倍
- if PA GE_SIZE-1 & unsigned longaddr { printkKERN_ERR "Trying to vfre bad address %p\n", addr;
- WA RN_ON1; return;
- } //移除对应的vm_area数据描述符,解除对各个物理页面的页面映射项
- area = remove_vm_areaaddr; if unlik!area {
- printkKERN_ERR "Trying to vfre nonexistent vm area %p\n", addr;
- WA RN_ON1; return;
- }
- debug_check_no_locks_freaddr, area->s;
- //需要向伙伴系统归还非连续的物理页 if deallocate_pag {
- int i; //将各个物理页面归还给伙伴系统
- for i = 0; i < area->nr_pages; i++ { BUG_ON!area->pages[i];
- __free_pagarea->pages[i]; }
- if area->flags & VM_VPA GES vfrearea->pag;
- else kfrearea->pag;
- }
- kfrearea;
- return; }
上一篇:传微软新任CEO选拔已进入一对一访谈阶段
下一篇:如何防止数据受前任员工破坏