kernel hacker修炼之道之内存管理-高端内存(下)

临时内核映射:


       固定映射的线性区从FIXADDR_START~FIXADDR_TOP,而临时内核映射区只是固定映射的线性区的一部分。固定映射用fixed_addresses中的索引从0xfffff000开始倒着往前分配固定地址的映射区。而临时内核映射其实就是永久映射的原子实现版本,它使用固定映射中FIX_KMAP_BEGIN到FIX_KMAP_END(它们都是的fixed_addresses中的枚举类型)这段区间。为了把一个物理地址与固定映射的线性地址关联起来,内核使用set_fixmap(idx, phys)和set_fixmap_nocache(idx, phys)宏。这两个函数都把fix_to_virt(idx)线性地址对应的一个页表项初始化为物理地址phys。 

view plain
  1. enum fixed_addresses {    
  2.     FIX_HOLE,    
  3.     FIX_VSYSCALL,    
  4. #ifdef CONFIG_X86_LOCAL_APIC    
  5.     FIX_APIC_BASE,  /* local (CPU) APIC) -- required for SMP or not */    
  6. #endif    
  7. #ifdef CONFIG_X86_IO_APIC    
  8.     FIX_IO_APIC_BASE_0,    
  9.     FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1,    
  10. #endif    
  11. #ifdef CONFIG_X86_VISWS_APIC    
  12.     FIX_CO_CPU, /* Cobalt timer */    
  13.     FIX_CO_APIC,    /* Cobalt APIC Redirection Table */     
  14.     FIX_LI_PCIA,    /* Lithium PCI Bridge A */    
  15.     FIX_LI_PCIB,    /* Lithium PCI Bridge B */    
  16. #endif    
  17. #ifdef CONFIG_X86_F00F_BUG    
  18.     FIX_F00F_IDT,   /* Virtual mapping for IDT */    
  19. #endif    
  20. #ifdef CONFIG_X86_CYCLONE_TIMER    
  21.     FIX_CYCLONE_TIMER, /*cyclone timer register*/    
  22. #endif     
  23. #ifdef CONFIG_HIGHMEM    
  24.     FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */    
  25.     FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,    
  26. #endif    
  27. #ifdef CONFIG_ACPI_BOOT    
  28.     FIX_ACPI_BEGIN,    
  29.     FIX_ACPI_END = FIX_ACPI_BEGIN + FIX_ACPI_PAGES - 1,    
  30. #endif    
  31. #ifdef CONFIG_PCI_MMCONFIG    
  32.     FIX_PCIE_MCFG,    
  33. #endif    
  34.     __end_of_permanent_fixed_addresses,    
  35.     /* temporary boot-time mappings, used before ioremap() is functional */    
  36. #define NR_FIX_BTMAPS   16    
  37.     FIX_BTMAP_END = __end_of_permanent_fixed_addresses,    
  38.     FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS - 1,    
  39.     FIX_WP_TEST,    
  40.     __end_of_fixed_addresses    
  41. };   
这里涉及到几个宏:
/*固定映射线性区的结束地址,距4G只有4KB*/

view plain
  1. #define FIXADDR_TOP ((unsigned long)__FIXADDR_TOP)    
/*固定映射线性区的大小*/
view plain
  1. #define __FIXADDR_SIZE  (__end_of_permanent_fixed_addresses << PAGE_SHIFT)    
/*固定映射的线性区起始地址*/
view plain
  1. #define FIXADDR_START       (FIXADDR_TOP - __FIXADDR_SIZE)    
/*计算给定索引对应的线性地址*/
view plain
  1. #define __fix_to_virt(x)    (FIXADDR_TOP - ((x) << PAGE_SHIFT))    
/*计算线性地址对应的索引*/
view plain
  1. #define __virt_to_fix(x)    ((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT)    

所以,每个索引对应的线性地址是不变的,但是可以通过set_fixmap和set_fixmap_nocache映射到不同的物理地址。

临时内核映射的枚举结构:

view plain
  1. enum km_type {    
  2. D(0)    KM_BOUNCE_READ,    
  3. D(1)    KM_SKB_SUNRPC_DATA,    
  4. D(2)    KM_SKB_DATA_SOFTIRQ,    
  5. D(3)    KM_USER0,    
  6. D(4)    KM_USER1,    
  7. D(5)    KM_BIO_SRC_IRQ,    
  8. D(6)    KM_BIO_DST_IRQ,    
  9. D(7)    KM_PTE0,    
  10. D(8)    KM_PTE1,    
  11. D(9)    KM_IRQ0,    
  12. D(10)   KM_IRQ1,    
  13. D(11)   KM_SOFTIRQ0,    
  14. D(12)   KM_SOFTIRQ1,    
  15. D(13)   KM_TYPE_NR    
  16. };    
这里每个type是一个“窗口”,每个CPU都有它自己的包含13个窗口的集合,他们用enum km_type数据结构表示,该数据结构中定义的每个符号,如KM_BOUNCE_READ,KM_USER0或KM_PTE0,标识了窗口的线性地址。在高端内存的任何一个页框都可以通过一个“窗口”映射到内核地址空间。也就是说一个窗口对应一个4KB的物理页。

kernel hacker修炼之道之内存管理-高端内存(下)_第1张图片

        内核必须确保同一个窗口永不会被两个不同的控制路径同时使用,也就是说后边的控制路径可以覆盖前边的。km_type结构中每个符号只能由一种内核成分使用,并以该成分命名。最后一个符号KM_TYPE_NR本身并不表示一个线性地址,但由每个CPU用来产生不同的可用窗口数。在km_type中每个符号都是固定映射的线性地址的一个下标。enum fixed_addresses数据结构包含符号FIX_KMAP_BEGIN和FIX_KMAP_END,把后者赋给下标FIX_KMAP_BEGIN + (KM_TYPE_NR * NR_CPUS) - 1。在这种情况下,系统中每个CPU都有KM_TYPE_NR个固定映射的线性地址。

建立临时内核映射调用kmap_atomic:

view plain
  1. void *kmap_atomic(struct page *page, enum km_type type)    
  2. {    
  3.     enum fixed_addresses idx;    
  4.     unsigned long vaddr;    
  5.     
  6.     /* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */    
  7.     inc_preempt_count();    
  8.     if (!PageHighMem(page))    
  9.         return page_address(page);    
  10.     
  11.     idx = type + KM_TYPE_NR*smp_processor_id();    
  12.     vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);    
  13. #ifdef CONFIG_DEBUG_HIGHMEM    
  14.     if (!pte_none(*(kmap_pte-idx)))    
  15.         BUG();    
  16. #endif    
  17.     set_pte(kmap_pte-idx, mk_pte(page, kmap_prot));    
  18.     __flush_tlb_one(vaddr);    
  19.     
  20.     return (void*) vaddr;    
  21. }    

       这里先判断是否是高端内存,如果不是就直接返回page对应的线性地址。否则,通过type和CPU标识符smp_processor_id()来确定在固定映射地址中的索引值。获得这个索引值对应的线性地址,设置相应的页表项,然后返回线性地址。这里会让人产生思考的地方是,为什么是kmap_pte-idx而不是kmap_pte+idx呢?先来看一下kmap_pte的初始化在内核启动的时候:

view plain
  1. void __init kmap_init(void)      
  2. {      
  3.     unsigned long kmap_vstart;      
  4.     /* cache the first kmap pte */      
  5.     kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);      
  6.     kmap_pte = kmap_get_fixmap_pte(kmap_vstart);      
  7.       
  8.     kmap_prot = PAGE_KERNEL;      
  9. }      
  10. #define kmap_get_fixmap_pte(vaddr)                  \      
  11.     pte_offset_kernel(pmd_offset(pud_offset(pgd_offset_k(vaddr), vaddr), (vaddr)), (vaddr))   

   通过对照上边的宏可以看出来,kmap_pteFIX_KMAP_BEGIN对应的线性地址所在的页表的页表的线性地址。由于使用的是__fix_to_virt宏,所以kmap_pte应该是接近FIXADDR_TOP而不是接近FIXADDR_START的。也就是说fixed_addresseskm_type中索引大的接近FIXADDR_START,索引小的接近FIXADDR_TOP。所以set_pte的时候是kmap_pte- idx
撤销临时内核映射调用kmap_atomic

view plain
  1. void kunmap_atomic(void *kvaddr, enum km_type type)    
  2. {    
  3. #ifdef CONFIG_DEBUG_HIGHMEM    
  4.     unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK;    
  5.     enum fixed_addresses idx = type + KM_TYPE_NR*smp_processor_id();    
  6.     
  7.     if (vaddr < FIXADDR_START) { // FIXME    
  8.         dec_preempt_count();    
  9.         preempt_check_resched();    
  10.         return;    
  11.     }    
  12.     
  13.     if (vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx))    
  14.         BUG();    
  15.     
  16.     /*  
  17.      * force other mappings to Oops if they'll try to access  
  18.      * this pte without first remap it  
  19.      */    
  20.     pte_clear(kmap_pte-idx);    
  21.     __flush_tlb_one(vaddr);    
  22. #endif    
  23.     
  24.     dec_preempt_count();    
  25.     preempt_check_resched();    
  26. }  <span style="line-height: 26px; "> </span>  

撤销的时候清除了相应的页表项。    

    综上,kernel中的高端内存已经研究完了。总结一下:高端内存的引入是为了kernel可以访问大于1G的物理内存(不是同一时刻),划出一个128MB的窗口来自由映射大于1G的内存。vmalloc()主要是建立动态分配和释放的内存区,但是建立和释放的过程非常复杂,需要对pgd,pud,pmd,pte进行修改。这里是修改masterkernel page globaldirectory,进程的内核页部分需要在访问时产生缺页异常然后再同步。而永久内核映射就简单的多,如果没有开PAE,则有4MB的线性地址可以用来映射,4MB当然是只有一个页表就够用了,这个专门的页表地址存放在pkmap_page_table变量中。只需要设置这个页表中相应的表项就可以了,一共1024个表项,每个对应一个4KB的页,因为页比较少,如果页耗尽的时候会导致进程阻塞,这样就不能用在中断处理程序中。而临时内核映射则更加简单了,其实就是永久内核映射的原子实现版,它利用固定内核映射中的一段空间,为每个CPU保存13个窗口,每个窗口的功能是固定的,不同进程需要分配同一个窗口的时候就进行覆盖,所以不会导致进程阻塞,可以用于中断处理程序和可延迟函数的内部。

你可能感兴趣的:(数据结构,timer,IO,user,DST,X86)