在i386体系结构中,高于896MB的物理内存被划分为高端内存,这部分物理内存并不是永久性的映射到内核地址空间,而是根据内核的需求取合适大小的内存临时映射到内核地址空间。这里需要先澄清两个概念:
如果物理内存超过1GB,那么内核地址空间是无法访问到所有内存的。为了解决这个问题,物理内存的0~896MB映射到内核地址空间0xc0000000~0xf8000000(3GB~3GB+896MB)。物理内存中超过896MB的部分则根据内核的需要,临时映射到内核地址空间剩下的128MB中。
页描述符,记录每个页框当前的状态,例如区分页框是属于内核还是用户进程,页框是否空闲。这些都保存在page类型的描述符中,所有描述符都保存在mem_map数组中。
散列表,用于建立高端内存页框与永久内核映射包含的线性地址之间的联系,由线性地址查找是否已有页框与之建立映射的时候可以提高查找速度。
page_address_htable包含的数据结构,用于为高端内存中每一个页框进行当前映射,包含一个指向页描述符的指针和分配给该页框的线性地址。
struct page_address_map { struct page *page; void *virtual; struct list_head list; };
指向永久内核映射的专用页表。该页表包含在普通页表中,需要在初始化过程中计算其具体地址,再由pkmap_page_table指向,由此可以快速引用。void __init permanent_kmaps_init(pgd_t *pgd_base) //持久内核映射,参数是cr3寄存器的值 { pgd_t *pgd; //全局页目录 pud_t *pud; //上层页目录 pmd_t *pmd; //中间页目录 pte_t *pte; //页目录 unsigned long vaddr; vaddr = PKMAP_BASE; //持久映射地址基址(虚拟地址) page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);//设置PKMAP_BASE~PKMAP_BASE+4MB线性地址段的页表 pgd = swapper_pg_dir + pgd_index(vaddr); //页全局目录地址 pud = pud_offset(pgd, vaddr); pmd = pmd_offset(pud, vaddr); pte = pte_offset_kernel(pmd, vaddr); pkmap_page_table = pte; //指向页表中pkmbase开始的地方,这个部分是所有内核页表共享的部分 }
void __init permanent_kmaps_init(pgd_t *pgd_base) //持久内核映射,参数是cr3寄存器的值 { pgd_t *pgd; //全局页目录 pud_t *pud; //上层页目录 pmd_t *pmd; //中间页目录 pte_t *pte; //页目录 unsigned long vaddr; vaddr = PKMAP_BASE; //持久映射地址基址(虚拟地址) page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);//设置PKMAP_BASE~PKMAP_BASE+4MB线性地址段的页表 pgd = swapper_pg_dir + pgd_index(vaddr); //页全局目录地址 pud = pud_offset(pgd, vaddr); pmd = pmd_offset(pud, vaddr); pte = pte_offset_kernel(pmd, vaddr); pkmap_page_table = pte; //指向页表中pkmbase开始的地方,这个部分是所有内核页表共享的部分 }
高端内存页框的数量。未开启PAE时为1024,开启PAE后为512。
持久内核映射的起始地址。
计数器数组,包含LAST_PKMAP个项,描述对应页表项的使用情况。值为0,对应页表项没有映射任何高端内存页框,可以使用;值为1,对应页表项没有映射任何高端内存页框,但它不能使用,因为自其最后一次使用以来,其相应的TLB还未刷新;值为n(大于1),对应的页表项映射一个高端内存页框,并且有n-1个内核成分正在使用它。
持久内核映射通常使用alloc_page函数获得可用页框,然后将指向页框描述符的指针传递给kmap函数建立到内核地址空间的持久映射。需要注意是的kmap可能会导致进程挂起,当可用于持久映射的4MB地址空间被使用完的时候就需要挂起进程,直到其他进程解除其中的一个地址映射关系。所以kmap不能被用于中断处理程序和软中断函数。经过一番研究,我将kmap的运行流程整理了出来,内核版本是Linux2.6.11
[课程]Linux pwn 探索篇!