Linux内核页表
2016-03-05 18:00
393 查看
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.
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为用户空间的地址值,之后才是内核空间的。
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的页表空间大小,这样就对应上了。
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,如标题所示。
( 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)
( 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,是内核空间的地址
进入内核空间页表的分配。
}
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上,当系统启动的时候,内核页表的地址是0x30004000, 内核的加载地址是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_SHIFT21 //地址中偏移位数,去前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,是内核空间的地址
进入内核空间页表的分配。
}
相关文章推荐
- linux及安全第二周总结——20135227黄晓妍
- Linux /etc/rc.d 下面 rc${runlevel}.d rc.local init.d 区别
- linux dmesg命令参数及用法详解(linux显示开机信息命令)
- CentOS7 yum 安装git
- Linux3.5内核以后的路由下一跳缓存
- Windows系统下通过xmanager远程桌面控制Linux
- Linux多线程与进程之间通信 实例2
- CentOS6.5 64位安装单机版hadoop2.6教程
- Linux3.5内核以后的路由下一跳缓存
- linux搭建nfs服务器
- 20135327郭皓——Linux内核分析第二周 操作系统是如何工作的
- Linux 多线程与进程间通信 实例 1
- CentOS 6.7下Android SDK adb 命令报错的解决方法
- linux下Eclipse安装启动后报错
- Linux
- Linux异步信号处理函数引发的死锁及解决方法
- Linux的运行级别和chkconfig用法
- Linux之解析鼠标input事件数据(有BUG,已经解决)
- Linux基础知识(三)
- Linux cloc