又是一个国庆七天假,之前有很多打算
可是到最后,只有linux愿意陪我。
介绍pageing_init之前,我们了解几个定义
pte_t 页表项
pmd_t 页中间目录项
pud_t 页上级目录
pgd_t 页全局目录项
我的arm平台
#define PMD_SHIFT 21
#define PGDIR_SHIFT 21
下面这个函数paging_init每个平台实现不一样,我的根本就没有用PUD_SHIFT
arm最多用二级
void __init paging_init(struct machine_desc*mdesc)
{
void *zero_page;
memblock_set_current_limit(lowmem_limit);
就是
memblock.current_limit = limit;
lowmem_limit = bank->start + bank->size;高端内存初始化时记录的我的是0x34000000
build_mem_type_table();这个函数很大,主要就是根据cpu类型记录内存信息,大的原因就是可虑了所以现有的arm类型
printk("Memory policy: ECC%sabled, Data cache %s\n",
ecc_mask ? "en" :"dis", cp->policy);
我的平台Memory policy: ECC disabled, Data cache writeback关闭ecc数据缓存为回写
prepare_page_table();
我们看看准备什么
static inline void prepare_page_table(void)
{
unsigned longaddr;
phys_addr_t end;
/*
* Clear out allthe mappings below the kernel image.
*/
清除内核下所有的映射
我的MODULES_VADDR=0xbf000000 PMD_SIZE=0x2000000
MODULES_VADDR是动态模块映射区起始地址,
PMD_SIZE宏用于计算由页中间目录的一个单独表项所映射的区域大小,也就是一个页表的大小
我的平台启动打印
vector : 0xffff0000 - 0xffff1000 ( 4kB)
fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
vmalloc : 0xc4800000 -0xf6000000 ( 792 MB)
lowmem : 0xc0000000 - 0xc4000000 ( 64MB)
pkmap : 0xbfe00000 - 0xc0000000 ( 2MB)
modules : 0xbf000000 -0xbfe00000 ( 14 MB)
for (addr = 0;addr < MODULES_VADDR; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
pmd_off_k(addr)就是(pmd_t *)addr
#definepmd_clear(pmdp) \
do{ \
pmdp[0] = __pmd(0); \
pmdp[1] = __pmd(0); \
clean_pmd_entry(pmdp); \
} while (0)
#define __pmd(x) (x)
staticinline void clean_pmd_entry(pmd_t *pmd)
{
说一下pmd传入此函数就是存在寄存器r0中
const unsigned int__tlb_flag = __cpu_tlb_flags;由cpu决定
if (tlb_flag(TLB_DCLEAN))先判断cpu内存类型
asm("mcr p15, 0, %0,c7, c10, 1 @ flush_pmd"
:: "r" (pmd) : "cc");
就是mcr p15 0 r0 c7 c10 1
清除数据缓冲区Line使用的装换的虚拟地址r0(就是pmd)。
if(tlb_flag(TLB_L2CLEAN_FR))
asm("mcr p15, 1, %0, c15, c9, 1 @ L2 flush_pmd"
:: "r" (pmd) : "cc");
清除L2 cache
}
上面以说过XIP。内
#ifdef CONFIG_XIP_KERNEL
/* The XIP kernelis mapped in the module area -- skip over it */
addr = ((unsignedlong)_etext + PMD_SIZE - 1) & PMD_MASK;
#endif
此时addr是内核空间地址开始
for ( ; addr <PAGE_OFFSET; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
从上面可以知道这个清除了动态模块空间、pkmap(高端内存的永久固定区)、内核低端内存空间
/*
* Find the end ofthe first block of lowmem.
*/
end =memblock.memory.regions[0].base + memblock.memory.regions[0].size;
if (end >=lowmem_limit)
end =lowmem_limit;
end设为低端内存空间结尾地址或第一个bank结尾地址
/*
* Clear out allthe kernel space mappings, except for the first
* memory bank, upto the end of the vmalloc region.
*/
当然是跳过第一个bank 的所以内核空间
VMALLOC_END=0xf6000000
for (addr =__phys_to_virt(end);
addr <VMALLOC_END; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
}
map_lowmem();
低端内存映射
static void __init map_lowmem(void)
{
structmemblock_region *reg;
/* Map all thelowmem memory banks. */
for_each_memblock(memory, reg) {
phys_addr_tstart = reg->base;
phys_addr_t end = start + reg->size;
structmap_desc map;
if (end> lowmem_limit)
end = lowmem_limit;
if (start>= end)
break;
map.pfn =__phys_to_pfn(start);页号
#define __phys_to_pfn(paddr) ((unsignedlong)((paddr) >> PAGE_SHIFT))
((unsigned long)((paddr) >>12))就是除以4096 即页大小
map.virtual = __phys_to_virt(start);
map.length= end - start;
map.type =MT_MEMORY;
create_mapping(&map);
生成目录项和必要的页表,这是一个重要的函数
static void __init create_mapping(struct map_desc *md)
{
unsigned longaddr, length, end;
phys_addr_t phys;
const struct mem_type *type;
pgd_t *pgd;
if (md->virtual!= vectors_base() && md->virtual < TASK_SIZE) {不是中断向量表地址也不是在内核空间
#if __LINUX_ARM_ARCH__ >= 4
#define vectors_high() (cr_alignment & CR_V)
#else
#define vectors_high() (0)
#endif
:#define vectors_base() (vectors_high() ? 0xffff0000 : 0)
中断向量表地址,我的是0xffff0000
#define TASK_SIZE (UL(CONFIG_PAGE_OFFSET) - UL(0x01000000))
0x01000000:16M就是动态模块空间+pkmap空间
TASK_SIZE就是内核空间地址开始
printk(KERN_WARNING "BUG: not creating mapping for 0x%08llx"
" at 0x%08lx in user region\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
return;
}
if ((md->type== MT_DEVICE || md->type == MT_ROM) &&
md->virtual>= PAGE_OFFSET && md->virtual < VMALLOC_END) {
重叠的虚拟空间
printk(KERN_WARNING "BUG: mapping for 0x%08llx"
" at 0x%08lx overlaps vmalloc space\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
}
type =&mem_types[md->type];
/*
* Catch 36-bitaddresses
*/
36位地址
if (md->pfn>= 0x100000) {
create_36bit_mapping(md, type);这个我们不看了,我还没有用过36位地址的
return;
}
#define PAGE_MASK (~(PAGE_SIZE-1)) 即~0xfff
addr =md->virtual & PAGE_MASK; 这样就是屏蔽页内偏移值
phys =__pfn_to_phys(md->pfn); 物理地址
length =PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));对齐
第12位为长度,高20位为页表和页目录
#define SECTION_SHIFT 20
#define SECTION_SIZE (1UL << SECTION_SHIFT) 段大小为1M
#define SECTION_MASK (~(SECTION_SIZE-1))
if(type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)){
此条件是即不是l1又是和1M对齐
下面细说这个判断
在下面的alloc_init_section()看到此条件的应用
printk(KERN_WARNING "BUG: map for 0x%08llx at 0x%08lx can not"
"be mapped using pages, ignoring.\n",
(long long)__pfn_to_phys(md->pfn), addr);
return;
}
pgd =pgd_offset_k(addr);
#define PGDIR_SHIFT 21
#define pgd_index(addr) ((addr) >> PGDIR_SHIFT) 右移21位正好的页目录地址
#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr))
#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
我懒得算直接打印把
printk(KERN_NOTICE"md->virtual = 0x%lx md->pfn = 0x%lx md->length = 0x%lx\n",(unsigned long)md->virtual, (unsigned long)md->pfn, (unsignedlong)md->length);
printk(KERN_NOTICE"addr = 0x%lx phys = 0x%lx length = 0x%lx init_mm.pgd = 0x%lx pgd =0x%lx\n", (unsigned long)addr, (unsigned long)phys, (unsigned long)length,(unsigned long)init_mm.pgd, (unsigned long)pgd);
此时打印结果
md->virtual = 0xc0000000 md->pfn = 0x30000 md->length =0x4000000
addr = 0xc0000000 phys = 0x30000000 length = 0x4000000 init_mm.pgd= 0xc0004000 pgd = 0xc0007000
end = addr +length;
do {
unsignedlong next = pgd_addr_end(addr, end);
#define pgd_addr_end(addr, end) \
({ unsigned long__boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK; \
(__boundary - 1 <(end) - 1)? __boundary: (end); \
})
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)
1 << 21 2M
#define PGDIR_MASK (~(PGDIR_SIZE-1)) 即屏蔽21位
这个就是找到下一个页目录
alloc_init_pud(pgd, addr, next, phys, type);
static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsignedlong end,
unsigned long phys,const struct mem_type *type)
{
传入的参数说明
pgd 页目录地址
addr 要映射的虚拟空间起始地址
end要映射的虚拟空间结束地址
phys 要映射的虚拟空间对应的物理空间起始地址
type mem类型
pud_t *pud =pud_offset(pgd, addr);
typedef struct { pgd_t pgd; } pud_t;
pud_offset() 就是转换为pgd_t因为我说过此平台没有pud
unsigned long next;
#define pud_addr_end(addr, end) (end)
do {
next =pud_addr_end(addr, end);
alloc_init_section(pud, addr, next, phys, type);这个函数我们在下面贴
phys += next - addr;
} while (pud++, addr= next, addr != end);
}
phys +=next - addr;
addr =next;
} while (pgd++,addr != end);
}
上面看似是pgd循环加上pud循环,在此实质就是pdg循环
}
}
下面重点看alloc_init_section
#definepmd_offset(dir, addr) ((pmd_t *)(dir))
在ARM处理器上,如果是整个段都有映射,则采用单层映射,如果不是整个段都有映射,则采用两层映射。页面大小采用的是4KB,使页面目录对应于ARM的首层映射表,中间目录设置成与页面目录相同,从而把概念上的三层映射转换成了物理上的两层映射。采用两层映射会降低系统的相应速度,因为从虚拟地址到物理地址之间的转换多了一步,会浪费时间,但是会增加内存的利用率,除非进程用完了3GB的地址空间。这种可能性是很小的。对于某些外设的操作,希望它反应迅速,因此需要将其进行单层映射,因此将IO寄存器所在的区域进行单层映射。
http://blog.csdn.net/zhaohc_nj/article/details/7977011
这个微博分析了alloc_init_section,可以看看,我也分析一下吧
static void __init alloc_init_section(pud_t *pud, unsigned longaddr,
unsignedlong end, phys_addr_t phys,
conststruct mem_type *type)
{
pmd_t *pmd =pmd_offset(pud, addr);
这个有让我们看到好像是pud循环下的pmd循环,其实都是pgd
/*
* Try a section mapping - end, addr and physmust all be aligned
* to a sectionboundary. Note that PMDs refer to theindividual
* L1 entries,whereas PGDs refer to a group of L1 entries making
* up one logicalpointer to an L2 table.
*/
解释的很清楚一个section映射。条件就是end, addr and physmust all be aligned
to a sectionboundary
if (((addr | end |phys) & ~SECTION_MASK) == 0) {
pmd_t *p =pmd;
if (addr& SECTION_SIZE)
pmd++;
do {
*pmd = __pmd(phys | type->prot_sect);
这里或上prot_sect和arm的mmu工作原理有关
phys += SECTION_SIZE;
可以看出就是把每个section的起始物理地址存入页目录地址
可以看出线性的概念。单层映射没有使用页框这个东西。就用了pgd.
} while (pmd++, addr += SECTION_SIZE,addr != end);
flush_pmd_entry(p);
flush_pmd_entry主要就是
asm("mcr p15, 0, %0,c7, c10, 1 @ flush_pmd"
: :"r" (pmd) : "cc");
就是r0 = p
mcr p15, 0, r0, c7, c10, 1
清除数据缓冲区Line使用的装换的虚拟地址
} else {
/*
* No needto loop; pte's aren't interested in the
*individual L1 entries.
*/
alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);
static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
unsigned long end, unsigned long pfn,
const struct mem_type *type)
{
pte_t *pte =early_pte_alloc(pmd, addr, type->prot_l1);
static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned longaddr, unsigned long prot)
{
if (pmd_none(*pmd)) {
pte_t *pte =early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
值为0即一级为空,此时要求pte分配空间512*sizof(pte_t) +512*sizeof(u32)
对于这个大小我们看看linux给我们的解释。arch/arm/include/asm/pgtable-2level.h
* Hardware-wise, we have a two level page table structure, wherethe first
* level has 4096 entries,and the second level has 256 entries. Each entry
* is one 32-bit word..Mostof the bits in the second level entry are used
* by hardware, and therearen't any "accessed" and "dirty" bits.
硬件方面,我们有一个两级页表结构,其中第一级有4096个条目,第二级有256个条目。每个条目是一个32-bit字。第二级有很多位被硬件用,它们中没有”accessed”和”dirty”位
* Linux on the other hand has a three level page table structure,which can
* be wrapped to fit a twolevel page table structure easily - using the PGD
* and PTE only. However, Linux also expects one"PTE" table per page, and
* at least a"dirty" bit.
另一方面Linux上有三级页表结构,它可以很容易包裹,以适应一个两级页表结构 -使用PGD和PTE。然而,Linux还预计,一个PTE表,每页至少一个“dirty”位。
* Therefore, we tweak the implementation slightly - we tell Linuxthat we
* have 2048 entries in thefirst level, each of which is 8 bytes (iow, two
* hardware pointers to thesecond level.) The second level containstwo
* hardware PTE tablesarranged contiguously, preceded by Linux versions
* which contain the stateinformation Linux needs. We, therefore,end up
* with 512 entries in the"PTE" level.
因此,我们小幅调整的实施 - 我们告诉Linux,我们有2048个条目,其中每8个字节(IOW,两个硬件指针到第二级)第二个等级包含两个硬件PTE表相邻排列,前面带有Linux版本,其中包含的Linux需要的状态信息。因此,我们有512个条目中的“pte”。
因此,linux在构造页表时,制造了一个假象:
1) 硬件PGD还是4096项,每项4字节;但是linux按照PGD共2048项,每项8字节来计算,计算得来的每项中实际都含有两个pgd项。
2) 硬件PTE还是256项,每项4字节;但是linux每次分配pte表时,都分配4K大小的页,页的前2048字节折合512个pte项,即折合两个硬件的pte表;页的后2048字节留作它用,折合为虚拟的512个pte项,即折合两个虚拟的pte表,与前半页对应。
这样,前半页折合出来的两个硬件pte表,正好填入2个pgd项中。
而且:
前半页折合出来的两个硬件pte表中,每项都包含一些页的属性bit,如是否present,是否可写....这些属性均为ARM硬件支持的属性,命名为PTE_xxx
后半页折合出来的两个虚拟pte表中,每项都包含一些页的属性bit,如是否accessed,是否dirty....这些属性均为ARM硬件不能支持的属性,命名为L_PTE_xxx
这样,就能模拟出来诸如accessed,dirty.....这样硬件还不支持的属性。
启动内存分配我在下次搞个单独微博再说
__pmd_populate(pmd, __pa(pte), prot);
static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,
pmdval_t prot)
{
pmdval_t pmdval =(pte + PTE_HWTABLE_OFF) | prot;
pmd 值为(pte + PTE_HWTABLE_OFF) | prot,这也是mmu工作原理决定的
pmdp[0] =__pmd(pmdval);第一个硬件pte表
pmdp[1] =__pmd(pmdval + 256 * sizeof(pte_t))第二个硬件pte表;
flush_pmd_entry(pmdp);已说过
}
}
BUG_ON(pmd_bad(*pmd));
returnpte_offset_kernel(pmd, addr);
}
do {
set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
pte的值也是和mmu硬件有关
#define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)
#define cpu_set_pte_ext __glue(CPU_NAME,_set_pte_ext)
#define __glue(name,fn) ____glue(name,fn)
#define ____glue(name,fn) name##fn
我的就是,真佩服linux内核开发者的用法
cpu_arm920_set_pte_ext(pfn, __pgprot(type->prot_pte)), 0);
这是汇编
/*
* cpu_arm920_set_pte(ptep,pte, ext)
*
* Set a PTE and flush it out设置并刷新
*/
.align 5
ENTRY(cpu_arm920_set_pte_ext)
#ifdef CONFIG_MMU
armv3_set_pte_ext
mov r0, r0
由于d cache打开, 这一条指令实际并没有写回内存,而是写到cache中
mcr p15, 0, r0, c7, c10, 1 @ clean D entry清除D入口
把cache中地址r0对应的内容写回内存中, 这一条语句实际是写到了write buffer中,
还没有真正写回内存。
mcr p15, 0, r0, c7, c10, 4 @ drain WBWB开漏
等待把writebuffer中的内容写回内存。
之前这个物理址可能已经与别的虚拟地址建立了映射,而且刚对该地址进行过操作,会出现在数据缓存中,因此除了要更新内存中的pte外,还要删除写缓冲器中与该中间目录项相对应的项和沥干写缓冲器,防止下一次进行存取时发生误操
#endif
mov pc, lr
pfn++;
} while (pte++, addr+= PAGE_SIZE, addr != end);
循环填写
}
}
}
devicemaps_init(mdesc);
/*
*Set up device the mappings. Since weclear out the page tables for all
*mappings above VMALLOC_END, we will remove any debug device mappings.
*This means you have to be careful how you debug this function, or any
*called function. This means you can'tuse any function or debugging
*method which may touch any device, otherwise the kernel _will_ crash.
*/
设置设备的映射。由于我们清除掉所有VMALLOC_END以上映射,我们将删除任何调试设备映射。这意味着你必须要小心调试此函数,或任何调用。这意味着你不能使用任何可能会碰触到任何设备的函数或调试方法,否则内核_will_崩溃。
这段话很好理解,比如我们用的硬件寄存器映射就是在vmalloc_end以上,当你在访问时,这个映射已被清除了,当然会崩溃。
static void __init devicemaps_init(structmachine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
/*
* Allocate the vector page early.
*/
vectors_page = early_alloc(PAGE_SIZE);
for (addr = VMALLOC_END; addr; addr +=PMD_SIZE)
pmd_clear(pmd_off_k(addr));
这个就是我们上面看的清除掉所有VMALLOC_END以上映射
#definepmd_clear(pmdp) \
do { \
pmdp[0] = __pmd(0); \
pmdp[1] = __pmd(0); \
clean_pmd_entry(pmdp); \
} while (0)
clean_pmd_entry()不贴了,就是利用cp15去操作pmd clear。
/*
* Map the kernel if it is XIP.
* It is always first in themodulearea.
*/
已说过xip,它代码段是运行在flash rom上,如norflash.
MODULES_VADDR=0xbf000000
从这看,内存也可以是flash啊
上面的英文已说的很清楚,xip总是从modulearea开始
我的平台没有这个
#ifdef CONFIG_XIP_KERNEL
map.pfn =__phys_to_pfn(CONFIG_XIP_PHYS_ADDR & SECTION_MASK);
map.virtual = MODULES_VADDR;
map.length = ((unsigned long)_etext -map.virtual + ~SECTION_MASK) & SECTION_MASK;
map.type = MT_ROM;
create_mapping(&map);
#endif
/*
* Map the cache flushing regions.
*/
Cache映射
#ifdef FLUSH_BASE
map.pfn =__phys_to_pfn(FLUSH_BASE_PHYS);
map.virtual = FLUSH_BASE;
map.length = SZ_1M;
map.type = MT_CACHECLEAN;
create_mapping(&map);
#endif
Minicache映射
#ifdefFLUSH_BASE_MINICACHE
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS+ SZ_1M);
map.virtual = FLUSH_BASE_MINICACHE;
map.length = SZ_1M;
map.type = MT_MINICLEAN;
create_mapping(&map);
#endif
上面的我的平台都没有
下面是向量表映射
/*
* Create a mapping for the machinevectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectorsvirtual address.
*/
map.pfn =__phys_to_pfn(virt_to_phys(vectors_page));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
map.type = MT_HIGH_VECTORS;
create_mapping(&map);
还记得我在此函数加的打印吧
打印结果
md->virtual = 0xffff0000 md->pfn = 0x33fff md->length =0x1000
addr = 0xffff0000 phys = 0x33fff000 length = 0x1000 init_mm.pgd =0xc0004000 pgd = 0xc0007ff8中断向量表的页目录地址
if (!vectors_high()) {
上面的英文说:如果不能用高端向量表,就映射个低端的,
从虚拟地址0开始
我们看看vectors_high()
#if__LINUX_ARM_ARCH__ >= 4
#definevectors_high() (cr_alignment & CR_V)
#else
#definevectors_high() (0)
#endif
这个是由arm架构来决定的
map.virtual = 0;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
/*
* Ask the machine support to map inthe statically mapped devices.
*/
if (mdesc->map_io)
mdesc->map_io();
静态映射:这个玩意就是我们说的io静态映射用法,我的平台有
.map_io = mini2440_map_io,
static void __initmini2440_map_io(void)
{
s3c24xx_init_io(mini2440_iodesc,ARRAY_SIZE(mini2440_iodesc));
}
static structmap_desc mini2440_iodesc[] __initdata = {
/* nothing to declare, move along */
};
这个我们只看两句
iotable_init(mach_desc, size);
iotable_init(s3c_iodesc,ARRAY_SIZE(s3c_iodesc));
static structmap_desc s3c_iodesc[] __initdata = {
IODESC_ENT(GPIO),
IODESC_ENT(IRQ),
IODESC_ENT(MEMCTRL),
IODESC_ENT(UART)
};
#define IODESC_ENT(x){ (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x,MT_DEVICE }
void __initiotable_init(struct map_desc *io_desc, int nr)
{
int i;
for (i = 0; i < nr; i++)
create_mapping(io_desc + i);
}
应该有四个打印
GPIO:
md->virtual = 0xfd000000 md->pfn = 0x56000 md->length =0x100000
addr = 0xfd000000 phys = 0x56000000 length = 0x100000 init_mm.pgd =0xc0004000 pgd = 0xc0007f40
IRQ:终端控制器
md->virtual = 0xf6000000 md->pfn = 0x4a000 md->length =0x100000
addr = 0xf6000000 phys = 0x4a000000 length = 0x100000 init_mm.pgd =0xc0004000 pgd = 0xc0007d80
MEMCTRL:存储控制器
md->virtual = 0xf6200000 md->pfn = 0x48000 md->length =0x100000
addr = 0xf6200000 phys = 0x48000000 length = 0x100000 init_mm.pgd =0xc0004000 pgd = 0xc0007d88
UART:
md->virtual = 0xf7000000 md->pfn = 0x50000 md->length =0x100000
addr = 0xf7000000 phys = 0x50000000 length = 0x100000 init_mm.pgd =0xc0004000 pgd = 0xc0007dc0
/*
* Finally flush the caches and tlb toensure that we're in a
* consistent state wrt thewritebuffer. This also ensures that
* any write-allocated cache lines inthe vector page are written
* back. After this point, we can start to touchdevices again.
*/
最后刷新caches和tlb以确保我们处于一致的writebuffer缓存。这也保证了任何写矢量页分配的cache lines被写回。这一点后,我们就可以开始再次触摸设备
local_flush_tlb_all();
flush_cache_all();
这两个都是汇编,我不在细看了
}
kmap_init();
这个函数时pkmap的初始化
这个就是我们说的高端内存中的永久内核映射,永久内存映射也是线性映射,它使用了散列表来记录页信息,此表的地址在PKMAP_BASE下,
#definePKMAP_BASE (PAGE_OFFSET -PMD_SIZE)
可以看出在0xc0000000下2M的地方0xbfe00000
static void __init kmap_init(void)
{
#ifdef CONFIG_HIGHMEM
pkmap_page_table =early_pte_alloc(pmd_off_k(PKMAP_BASE),
PKMAP_BASE, _PAGE_KERNEL_TABLE);
#endif
}
top_pmd = pmd_off_k(0xffff0000);
/* allocate the zero page. */
zero_page = early_alloc(PAGE_SIZE);
bootmem_init();
bootmem_init下一篇微博单独看
empty_zero_page = virt_to_page(zero_page);
__flush_dcache_page(NULL, empty_zero_page);
不细看
}