前面总结了高端内存中永久内核映射和临时内核映射。linux高端内存中的临时内存区为固定内存区的一部分,下面是Linux内存布局图
对于固定内存在linux内核中有下面描述
enum fixed_addresses { #ifdef CONFIG_X86_32 FIX_HOLE, FIX_VDSO, #else VSYSCALL_LAST_PAGE, VSYSCALL_FIRST_PAGE = VSYSCALL_LAST_PAGE + ((VSYSCALL_END-VSYSCALL_START) >> PAGE_SHIFT) - 1, VSYSCALL_HPET, #endif FIX_DBGP_BASE, FIX_EARLYCON_MEM_BASE, #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_X86_32 FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */ FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1, #ifdef CONFIG_PCI_MMCONFIG FIX_PCIE_MCFG, #endif #endif #ifdef CONFIG_PARAVIRT FIX_PARAVIRT_BOOTMAP, #endif FIX_TEXT_POKE1, /* reserve 2 pages for text_poke() */ FIX_TEXT_POKE0, /* first page is last, because allocation is backward */ __end_of_permanent_fixed_addresses, /* * 256 temporary boot-time mappings, used by early_ioremap(), * before ioremap() is functional. * * We round it up to the next 256 pages boundary so that we * can have a single pgd entry and a single pte table: */ #define NR_FIX_BTMAPS 64 #define FIX_BTMAPS_SLOTS 4 FIX_BTMAP_END = __end_of_permanent_fixed_addresses + 256 - (__end_of_permanent_fixed_addresses & 255), FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS*FIX_BTMAPS_SLOTS - 1, #ifdef CONFIG_PROVIDE_OHCI1394_DMA_INIT FIX_OHCI1394_BASE, #endif #ifdef CONFIG_X86_32 FIX_WP_TEST, #endif #ifdef CONFIG_INTEL_TXT FIX_TBOOT_BASE, #endif __end_of_fixed_addresses };
固定映射
ioremap的作用是将IO和BIOS以及物理地址空间映射到在896M至1G的128M的地址空间内,使得kernel能够访问该空间并进行相应的读写操作。
start_kernel()->setup_arch()->early_ioremap_init()
void __init early_ioremap_init(void) { pmd_t *pmd; int i; if (early_ioremap_debug) printk(KERN_INFO "early_ioremap_init()\n"); /*将fixed_address里的索引的虚拟地址放入slot_virt ,从代码里面可以看出,放入slot_virt中得虚拟地址为1M*/ for (i = 0; i < FIX_BTMAPS_SLOTS; i++) slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i); /*得到固定映射区的pmd ,此pmd为虚拟地址转换为物理地址的pmd*/ pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)); memset(bm_pte, 0, sizeof(bm_pte)); /*将bm_pte页表设置为固定映射区开始地址的pmd的第一个页表;*/ pmd_populate_kernel(&init_mm, pmd, bm_pte); /* * The boot-ioremap range spans multiple pmds, for which * we are not prepared: */ /*系统要求所有的ioremap映射在一个pmd上,超出这个pmd将警告*/ if (pmd != early_ioremap_pmd(fix_to_virt(FIX_BTMAP_END))) { WARN_ON(1); printk(KERN_WARNING "pmd %p != %p\n", pmd, early_ioremap_pmd(fix_to_virt(FIX_BTMAP_END))); printk(KERN_WARNING "fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n", fix_to_virt(FIX_BTMAP_BEGIN)); printk(KERN_WARNING "fix_to_virt(FIX_BTMAP_END): %08lx\n", fix_to_virt(FIX_BTMAP_END)); printk(KERN_WARNING "FIX_BTMAP_END: %d\n", FIX_BTMAP_END); printk(KERN_WARNING "FIX_BTMAP_BEGIN: %d\n", FIX_BTMAP_BEGIN); } }
static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __initdata;
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
其中FIXADDR_TOP为4G-4K。
对于ioremap的使用需要通过early_memremap和early_iounmap进行。由于对应于ioremap的内存空间是有限的,所以对于ioremap空间的使用遵照使用结束马上释放的原则。这就是说early_memremap和early_iounmap必须配对使用并且访问结束必须马上执行unmap。
static void __init __iomem * __early_ioremap(resource_size_t phys_addr, unsigned long size, pgprot_t prot) { unsigned long offset; resource_size_t last_addr; unsigned int nrpages; enum fixed_addresses idx0, idx; int i, slot; WARN_ON(system_state != SYSTEM_BOOTING); slot = -1; /*pre_map[]是一个索引与slot_virt[]一一对应,这段for 的含义在于找到一个没有被使用过的slot_virt[i]的页面, 该slot_virt[i]所指向的虚拟页面地址就是将会和实际物 理地址phys_addr相绑定的虚拟地址。*/ for (i = 0; i < FIX_BTMAPS_SLOTS; i++) { if (!prev_map[i]) { slot = i; break; } } if (slot < 0) { printk(KERN_INFO "early_iomap(%08llx, %08lx) not found slot\n", (u64)phys_addr, size); WARN_ON(1); return NULL; } if (early_ioremap_debug) { printk(KERN_INFO "early_ioremap(%08llx, %08lx) [%d] => ", (u64)phys_addr, size, slot); dump_stack(); } /* Don't allow wraparound or zero size */ last_addr = phys_addr + size - 1; if (!size || last_addr < phys_addr) { WARN_ON(1); return NULL; } prev_size[slot] = size; /* * Mappings have to be page-aligned */ offset = phys_addr & ~PAGE_MASK;/*offset是页内的偏移*/ phys_addr &= PAGE_MASK;/*现在phys_addr就是起始页面的地址*/ /*现在size就是指出了到底占据了多少个页面的大小*/ size = PAGE_ALIGN(last_addr + 1) - phys_addr; /* * Mappings have to fit in the FIX_BTMAP area. */ /*到底我们需要多少页面*/ nrpages = size >> PAGE_SHIFT; if (nrpages > NR_FIX_BTMAPS) { WARN_ON(1); return NULL; } /* * Ok, go for it.. */ /*找到空闲slot所对应的fixed_address中的索引号*/ idx0 = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot; idx = idx0; while (nrpages > 0) { /*在bm_ptes中将指定的idx索引的页表项填充为对应的物理地址使得bm_pte[idx]指向正确的物理页面地址*/ early_set_fixmap(idx, phys_addr, prot); phys_addr += PAGE_SIZE; --idx; --nrpages; } if (early_ioremap_debug) printk(KERN_CONT "%08lx + %08lx\n", offset, slot_virt[slot]); /*返回phys_addr所指向的虚拟地址*/ prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]); return prev_map[slot]; }