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

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。

enum fixed_addresses { FIX_HOLE, FIX_VSYSCALL, #ifdef CONFIG_X86_LOCAL_APIC FIX_APIC_BASE, /* local (CPU) APIC) -- required for SMP or not */ #endif #ifdef CONFIG_X86_IO_APIC FIX_IO_APIC_BASE_0, FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1, #endif #ifdef CONFIG_X86_VISWS_APIC FIX_CO_CPU, /* Cobalt timer */ FIX_CO_APIC, /* Cobalt APIC Redirection Table */ FIX_LI_PCIA, /* Lithium PCI Bridge A */ FIX_LI_PCIB, /* Lithium PCI Bridge B */ #endif #ifdef CONFIG_X86_F00F_BUG FIX_F00F_IDT, /* Virtual mapping for IDT */ #endif #ifdef CONFIG_X86_CYCLONE_TIMER FIX_CYCLONE_TIMER, /*cyclone timer register*/ #endif #ifdef CONFIG_HIGHMEM FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */ FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1, #endif #ifdef CONFIG_ACPI_BOOT FIX_ACPI_BEGIN, FIX_ACPI_END = FIX_ACPI_BEGIN + FIX_ACPI_PAGES - 1, #endif #ifdef CONFIG_PCI_MMCONFIG FIX_PCIE_MCFG, #endif __end_of_permanent_fixed_addresses, /* temporary boot-time mappings, used before ioremap() is functional */ #define NR_FIX_BTMAPS 16 FIX_BTMAP_END = __end_of_permanent_fixed_addresses, FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS - 1, FIX_WP_TEST, __end_of_fixed_addresses }; 这里涉及到几个宏:
/*固定映射线性区的结束地址,距4G只有4KB*/

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

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

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

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


内核必须确保同一个窗口永不会被两个不同的控制路径同时使用,也就是说后边的控制路径可以覆盖前边的。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:

void *kmap_atomic(struct page *page, enum km_type type) { enum fixed_addresses idx; unsigned long vaddr; /* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */ 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); #ifdef CONFIG_DEBUG_HIGHMEM if (!pte_none(*(kmap_pte-idx))) BUG(); #endif set_pte(kmap_pte-idx, mk_pte(page, kmap_prot)); __flush_tlb_one(vaddr); return (void*) vaddr; }

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

void __init kmap_init(void) { unsigned long kmap_vstart; /* cache the first kmap pte */ kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN); kmap_pte = kmap_get_fixmap_pte(kmap_vstart); kmap_prot = PAGE_KERNEL; } #define kmap_get_fixmap_pte(vaddr) \ 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

void kunmap_atomic(void *kvaddr, enum km_type type) { #ifdef CONFIG_DEBUG_HIGHMEM unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK; enum fixed_addresses idx = type + KM_TYPE_NR*smp_processor_id(); if (vaddr < FIXADDR_START) { // FIXME dec_preempt_count(); preempt_check_resched(); return; } if (vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx)) BUG(); /* * force other mappings to Oops if they'll try to access * this pte without first remap it */ pte_clear(kmap_pte-idx); __flush_tlb_one(vaddr); #endif dec_preempt_count(); preempt_check_resched(); }

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

<style type="text/css"> <!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } --> </style>

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值