您的位置:首页 > 运维架构 > Linux

Linux内核页表

2016-03-05 18:00 393 查看


Linux内核页表

一. Linux地址空间

ARM的32位系统共支持4G的内存空间,其中0-3G为用户空间,3G-4G是内核空间,

ARM采用2级页表,32位地址空间ADDRESS分别为
PGD|PTE|12Bits,
在内核代码中分别为PGD 11位,PTE 9
位,页内地址12位;但是在MMU系统中对于ARM的二级分页设置分别为PGD
12位,PTE 8位,页内地址为12位。在内核代码层次虽然是11位,但是经过代码中的设置,最后都映射到MMU起作用时的PGD12位,PTE8位。看代码定义。

#define PTRS_PER_PGD
2048 //PGD页中的指针数

#definePGDIR_SHIFT 21
//地址中偏移位数,去前11位,因为要右移21位

#define PGDIR_SIZE
(1UL<< PGDIR_SHIFT) 0x20 0000

#define USER_PTRS_PER_PGD
(TASK_SIZE/ PGDIR_SIZE)

在linux-3.5中,用户空间大小TASK_SIZE定义如下

#define TASK_SIZE
(UL(CONFIG_PAGE_OFFSET)- UL(0x01000000))

#define CONFIG_PAGE_OFFSET 0xC000 0000

在linux-2.6.25中,用户空间大小TASK_SIZE定义

#define TASK_SIZE 0xC000 0000

二. 页表

页表分为用户空间页表和内核空间页表,不同的进程,它用户空间是不同的,所以它的用户空间页表是不同的,但是不同的进程它的内核空间是共享的,它的内核空间页表也是相同的。

在创建一个进程时,会为它创建一个页表指针,即mm_struct数据结构中的pgd_t
* pgd;分配的函数是mm_init ()->mm_alloc_pgd(struct mm_struct *mm) ->pgd_alloc(structmm_struct *mm) ->#define __pgd_alloc()
(pgd_t*)__get_free_pages(GFP_KERNEL, 2),分配的空间为16k,
即4096*4bytes,从此处分配的页表空间包括了用户空间页表和内核空间页表。

用户空间页表映射了地址0x0000 0000到0xC000
0000的空间,即为TASK_SIZE的大小。然后

#define USER_PTRS_PER_PGD (TASK_SIZE/PGDIR_SIZE)=0xC0000000/0x20 0000 =0x600=1536

内核空间的页表映射了地址0xC000 0000到0Xffff
ffff的空间,为1G,然后

0x40000000/PGDIR_SIZE=0x4000 0000/0x20 0000 = 512,

所以页表的项数共为1536+512 =2048项。

考虑到硬件的地址映射,pgd实际为12位,即只需要右移20位,而不是21位,PGDIR_SHIFT的数值,所以实际上页表的项数实际应该为2048*2的4096个项数,然后每项为4个字节,最后与上文分配的16K地址对应起来,即4096*4bytes=16k.

三. 引导代码的内核页表分配

Linux-2.6的S3C2410上,当系统启动的时候,内核页表的地址是0x3000
4000, 内核的加载地址是0x3000 8000,所以内核页表的最大空间是0x4000,4字节,也为16K。

系统启动的时候,最先执行的是最后会成为空闲进程init_mm(),
该进程在启动结束前会生成内核进程init进程,然后再启动其他进程。在init_mm()结构中定义了.pgd
= swapper_pg_dir,

#define CONFIG_PAGE_OFFSET 0xC000 0000

#definePAGE_OFFSET
UL(CONFIG_PAGE_OFFSET)

#defineKERNEL_RAM_VADDR
(PAGE_OFFSET +TEXT_OFFSET)

.equ
swapper_pg_dir,KERNEL_RAM_VADDR - PG_DIR_SIZE

#definePG_DIR_SIZE
0x4000

空闲进程的页表初始化在引导代码的汇编中实现,__create_page_tables

四. 进程内核空间页表的分配

上面已经讨论了进程页表分为用户空间页表和内核空间页表,当进程创建的时候会把

new_pgd = __pgd_alloc();

if (!new_pgd)

goto no_pgd;

memset(new_pgd, 0, USER_PTRS_PER_PGD *sizeof(pgd_t));

/*

* Copy over the kernel and IO PGD entries

*/

init_pgd = pgd_offset_k(0);

memcpy(new_pgd + USER_PTRS_PER_PGD,init_pgd + USER_PTRS_PER_PGD,

(PTRS_PER_PGD - USER_PTRS_PER_PGD) *sizeof(pgd_t));

//////////////////////////////////////////////////////////////////////////////////////

以下是对pgd_offset_k(0)的解析,最后init_pgd即为swapper_pg_dir的值

#definepgd_index(addr)
((addr)>> PGDIR_SHIFT)

#definepgd_offset(mm, addr)
((mm)->pgd +pgd_index(addr))

#definepgd_offset_k(addr)
pgd_offset(&init_mm,addr)

/////////////////////////////////////////////////////////////////////////////////////

Memcpy()函数中参数的解析

#definePTRS_PER_PGD
2048,总的页数(暂时这么说)。

#defineUSER_PTRS_PER_PGD
(TASK_SIZE /PGDIR_SIZE) 1536

前1536为用户空间的地址值,之后才是内核空间的。

五.11位和12位的问题

#define PGDIR_SHIFT
21 //地址中偏移位数,去前11位,因为要右移21位

#define PGDIR_SIZE
(1UL<< PGDIR_SHIFT) 0x20 0000

ARM二级页表中PGD的位数为12,
即只要左移20为就行了,但是代码中却移动了21为,即多除了一个2;软件中相当于11位,那就可以这样理解,11位+1位,最后在11位的基础上补上0和1两个值来补齐12位,这样软件与硬件就对应起来。这样,前文中PGD页表的运算是根据11位来运算的,多除了一个2,现在补上0或者1后,它的数值应该翻倍,所以总的页表项数,应该为:

用户空间页表映射了地址0x0000 0000到0xC000
0000的空间,即为TASK_SIZE的大小。然后

#define USER_PTRS_PER_PGD (TASK_SIZE/PGDIR_SIZE)=0xC0000000/0x20 0000 =0x600=1536
// 1536*2 = 3072

内核空间的页表映射了地址0xC000 0000到0Xffff
ffff的空间,为1G,然后

0x40000000/PGDIR_SIZE=0x4000 0000/0x20 0000 = 512, // 512*2 = 1024

所以页表的项数共为
1536+512=2048项。 //2048*2 =4096

然后每项为4字节,
4096*4 = 16K , 即为每个进程分配的PGD的页表空间大小,这样就对应上了。

六.用户空间页表的分配

七.内核空间页表的分配(以linux-3.5为例)

在初始化内核空间页表时,大部分的内核空间已经映射好了,如内核代码的地址,内核内存地址,影响内核页表的内存分配主要有永久内核映射,临时内核映射,非连续内存分配等,当分配内存的时候,内核更新swapper_pg_dir地址下的页表,当访问该地址时,就会产生取值异常,然后就内核就把相应的内核映射的对应项复制到进程的相应的内核页表项中。

7.1
内核空间地址内存的分配

在系统初始化的时候,内核就会初始化内核内存的内核空间页表,

void __init paging_init(struct machine_desc *mdesc)

{

……

map_lowmem();--àcreate_mapping(&map,false);

……

}

7.2
永久内核映射

pkmap : 0xbfe00000 - 0xc0000000
( 2 MB)

kmap()

{

might_sleep();

if(!PageHighMem(page))

returnpage_address(page);

returnkmap_high(page);

}

#define PKMAP_BASE
(PAGE_OFFSET - PMD_SIZE) //0xbfe00000

#define PAGE_OFFSET 0xC000 0000

#define PMD_SHIFT 21

#define PMD_SIZE (1UL << PMD_SHIFT)

#define LAST_PKMAP
PTRS_PER_PTE //512

#define LAST_PKMAP_MASK
(LAST_PKMAP - 1) //511

last_pkmap_nr = (last_pkmap_nr + 1) &LAST_PKMAP_MASK;

512 * 4k = 2 M,如标题所示。

7.3临时内核映射

fixmap : 0xfff00000 - 0xfffe0000
( 896kB)

void *kmap_atomic(struct page *page)

4K一个页面

#define FIXADDR_START
0xfff00000UL

#define FIXADDR_TOP
0xfffe0000UL

#define FIXADDR_SIZE
(FIXADDR_TOP - FIXADDR_START)

#define FIX_KMAP_BEGIN
0

#define FIX_KMAP_END
(FIXADDR_SIZE >> PAGE_SHIFT)

7.4非连续内存分配

vmalloc : 0xe4800000 - 0xfc000000
( 376 MB)

vmalloc()

#define VMALLOC_START (((unsignedlong)high_memory + VMALLOC_OFFSET) & ~

(VMALLOC_OFFSET-1))

#define VMALLOC_END
0xff000000UL

high_memory = __va(arm_lowmem_limit - 1) +1;
//此处定义有疑义;

vmalloc -> __vmalloc_node_flags->__vmalloc_node -> __vmalloc_node_range ->

__vmalloc_area_node -> (此处可能有递归但最终会调用此函数:)
map_vm_area ->

vmap_page_range ->vmap_page_range_noflush -> vmap_pud_range -> vmap_pmd_range ->

vmap_pte_range -> pte_alloc_kernel ->__pte_alloc_kernel -> pmd_populate_kernel

(&init_mm, pmd, new)->__pmd_populate(pmdp, __pa(ptep), _PAGE_KERNEL_TABLE)

static inline void __pmd_populate(pmd_t*pmdp, phys_addr_t pte,pmdval_t prot)

{

pmdval_tpmdval = (pte + PTE_HWTABLE_OFF) | prot;

pmdp[0]= __pmd(pmdval);

#ifndef CONFIG_ARM_LPAE

pmdp[1]= __pmd(pmdval + 256 * sizeof(pte_t));

#endif

flush_pmd_entry(pmdp);

}

八.空值异常

当内核访问某个地址发生异常时就会触发取值异常,从中断中进入函数

do_translation_fault(unsigned long addr,unsigned int fsr,

struct pt_regs *regs)

{

if(addr < TASK_SIZE)

returndo_page_fault(addr, fsr, regs); //用户空间的内存分配

如果地址大于TASK_SIZE,即 0xC000 0000,是内核空间的地址

进入内核空间页表的分配。

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: