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

本文深入探讨了Linux内核高端内存管理机制,重点介绍了临时内核映射的实现原理及其与永久内核映射的区别。详细解释了固定映射、临时内核映射的使用方法和流程。

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个窗口,每个窗口的功能是固定的,不同进程需要分配同一个窗口的时候就进行覆盖,所以不会导致进程阻塞,可以用于中断处理程序和可延迟函数的内部。

Linux常见驱动源码分析(kernel hacker修炼)--李万鹏 李万鹏 IBM Linux Technology Center kernel team 驱动资料清单内容如下: Linux设备模型(中)之上层容器.pdf Linux设备模型(上)之底层模型.pdf Linux驱动修炼-驱动中一些常见的宏.pdf Linux驱动修炼-内存映射.pdf Linux驱动修炼-看门狗框架源码分析.pdf Linux驱动修炼-触摸屏驱动之s3c2410_ts源码分析.pdf Linux驱动修炼-SPI驱动框架源码分析().pdf Linux驱动修炼-SPI驱动框架源码分析().pdf Linux驱动修炼-SPI驱动框架源码分析().pdf Linux驱动修炼-RTC子系统框架源码分析.pdf Linux驱动修炼-platform.pdf Linux驱动修炼-LCD背光gpio控制.pdf Linux驱动修炼-INPUT子系统().pdf Linux驱动修炼-INPUT子系统().pdf Linux驱动修炼-framebuffer(中).pdf Linux驱动修炼-framebuffer(下).pdf Linux驱动修炼-framebuffer(上).pdf Linux驱动修炼-DMA框架源码分析().pdf Linux驱动修炼-DMA框架源码分析().pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析().pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析().pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析().pdf Linux驱动修炼-clock框架.pdf Linux驱动修炼-ADC驱动.pdf Linux内核访问外设I O资源的方式.pdf LINUX内核USB子系统学习笔记之初识USB.pdf kernel hacker修炼之驱动-流水灯.pdf kernel hacker修炼之驱动-混杂设备.pdf kernel hacker修炼之驱动-按键.pdf kernel hacker修炼之PCI subsystem().pdf kernel hacker修炼之PCI subsystem().pdf kernel hacker修炼之PCI subsystem().pdf kernel hacker修炼之PCI subsystem().pdf kernel hacker修炼之PCI subsystem().pdf
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值