精华内容
下载资源
问答
  • 页表页表的存在很好的协调了物理内存和虚拟内存,从而很好的提高系统的安全性。在一个进程里面使用的内存可能是很少的一部分,但是存在的实际内存确实很大的,所以在使用页表的时候可能会映射很小的一部分...

    在使用的计算机内存(4G)上面用户可以使用的内存只有0~3G,3~4G是系统内核使用的区域。

    35be9a915acb7131fc437fbdfd9cc8d0.png

    但是在物理内存上面,任何的区域和位置都是可读可写操作的,假如进程直接访问的是物理内存,那么系统就会存在很大的不安全性。

    页表

    页表的存在很好的协调了物理内存和虚拟内存,从而很好的提高系统的安全性。

    在一个进程里面使用的内存可能是很少的一部分,但是存在的实际内存确实很大的,所以在使用页表的时候可能会映射很小的一部分。

    d10454d3960f14d532df3451ac1feac5.png

    由于每个进程都存在自己的页表数据结构,那么很有可能在相同的虚拟内存上面看到不一样的数据,那时候因为在用户看到的内存地址都是虚拟地址,而数据存储是在物理内存,物理地址的不同则数据就会不同。

    多级页表

    假如在系统里面,物理内存和虚拟内存是一一对应的,那么在进程空间里面就会存在很多的页表,同时也会占据很多的空间,那么解决这个问题就出现了多级页表。

    liunx里面使用的4级页表:

    4d82ca3f22d218bac5374ca0dc3bc0cd.png

    在计算机系统体系结构里面,将页表划分成不同的项,从而不同的项保存地址的不同位。

    - cr3里面保存的是进程的PID

    地址的翻译过程:

    - bit22~bit31作为页目录索引项,在cr3里面找到相应的页目录项

    - bit12~bit21作为寻找到的页目录项的页表索引,在页表项里面找到相应的页表

    - bit0~bit11作为页表索引项,在页内找到也得偏移量和页得到相应的物理地址

    所以在进程进行替换的时候只需要将cr3的指向替换即可

    展开全文
  • Linux页表与ARM硬件页表说明ARM二级页表映射关系 说明 Kernel版本:4.0.0 ARM处理器,Contex-A9,QEMU平台   内核初始化arm页表的内容,low_memory映射过程,之前也大概写了。但是在arm平台中,引入了硬件页表、...

    说明

    Kernel版本:4.0.0
    ARM处理器,Contex-A9,QEMU平台
      内核初始化arm页表的内容,low_memory映射过程,之前也大概写了。但是在arm平台中,引入了硬件页表、linux页表的概念,本文描述为何要这样处理,以及内核、arm硬件页表属性进行说明。
      参考书籍《奔跑吧Linux内核:基于Linux4.x内核源代码问题分析》2.2.1 ARM32页表映射。

    ARM二级页表映射关系

      Linux中ARM32位架构采用2级页表映射方式,MMU查找地址关系如下图,画图麻烦,直接从书抄了一张过来。
    ARM MMU映射过程
      当片上TLB中不包含要求访问的虚拟地址入口是时,转换过程被启动。转换表基地址寄存器保存着第一级转换表基址的物理地址。只有bit[31:14]有效,bits[13:0]应该是0。所以第一级页表必须16KB对齐。对应的32位系统中,section大小1M,总共4096个section,每个section 4个字节,所以一级页表大小是16Kb。
      ARM MMU页表如上图所示。如果采用单层的段映射,内存中有个段映射表,表中4096个表项,每个表项的大小是4Byte,所以这个段映射表的大小是16KB。每个段对应1MB大小的地址空间。当cpu访问内存时,32位虚拟地址的高12位用于访问段映射表的索引,从表中找到相应的表项,每个表项提供了一个12位的物理段地址,以及相应的标志位,如可读、可写等标志位。将这个12位物理地址和虚拟地址的低20位拼凑在一起,就可以得到32位的物理地址。linux初始化时,内核low memory使用的大部分是段映射的方式。
      如果采用页表映射的方式,段映射表就变成一级映射表,linux内核中成为pmd。其表项提供的不再是物理段地址,而是二级页表的基地址。32位虚拟地址的高12位作为一级页表的索引值,找到相应的一级页表,每个表项指向一个二级页表。从虚拟地址的次8位作为访问二级页表的索引值,得到相应的页表项,从这个页表项中找到20位的物理页面地址。最后将这20位物理页面地址和虚拟地址的低12位拼凑在一起,得到最终的32位物理地址。这个过程在ARM32架构中由MMU硬件完成,软件不需要介入。

    Linux页表机制

      早期Linux内核是基于x86体系结构设计的,x86页表中有3个标志位是ARM32硬件页表没有的。
    PTE_DIRTY:cpu在写操作时会设置该标志位,表示对应页面被写过,为脏页。
    PTE_YOUNG:CPU访问该页时会设置该标志位。在页面换出时,如果该标志位位置了,说明该页刚被访问过,页面是young的,不适合把该页换出,同时清除该标志位。
    PTE_PRESENT:表示页在内存中。
      针对这种情况,Linux维护了一份Linux版本的页表,用于模拟这些标志位。由于一个页面是4KB大小,每个section 1MB空间,需要256个pte页表项,占用1KB,Linux版本也需要占用1KB,所以Linux创建二级页表时,以2MB为单位,申请1个PAGE存放对应的页表项。前2KB存放linux版本页表,后2KB存放硬件页表。存放地址如下。

     *    pgd             pte
     * |        |
     * +--------+
     * |        |       +------------+ +0
     * +- - - - +       | Linux pt 0 |
     * |        |       +------------+ +1024
     * +--------+ +0    | Linux pt 1 |
     * |        |-----> +------------+ +2048
     * +- - - - + +4    |  h/w pt 0  |
     * |        |-----> +------------+ +3072
     * +--------+ +8    |  h/w pt 1  |
     * |        |       +------------+ +4096
    

    在这里插入图片描述

    ARM/Linux PTE属性

    ARM硬件PTE属性

      下图表示ARM硬件二级页表属性,Linux内核使用的是Small page。在这里插入图片描述

      可以看到每个页表项中不仅包含了下级页表或者物理地址的映射地址(除了无效页表项),而且包含了诸如:TEX S SBZ AP 等属性位,这些属性位影响着 CPU 对内存的访问动作。
    C 位代表是否cache。
    B 位代表是否使能高速buffer。
    TEX C B 用来设置内存种类与缓存策略。正常内核开发时,需要关心的基本也就是是否使能cache和buffer。
    APX/AP 被称作访问权限标志位,规定了映射到的内存的访问权限:可读、可写、只读等等。
    S 标志某块内存的共享性,设置了标志位 S 的表项所映射的物理内存是可共享的,反之则是非可共享的。
    XN 代表可执行,设置了表示页面不可执行。

    内核预定义宏

      内核中预定义了一些宏,用于页表属性设置,包括Linux版本页表,以及硬件版本页表。
      先看Linux版本页表对应的宏,L开头代表Linux。由于使用2级映射,所以宏位于pgtable-2level.h。

    /*
     * "Linux" PTE definitions.
     *
     * We keep two sets of PTEs - the hardware and the linux version.
     * This allows greater flexibility in the way we map the Linux bits
     * onto the hardware tables, and allows us to have YOUNG and DIRTY
     * bits.
     *
     * The PTE table pointer refers to the hardware entries; the "Linux"
     * entries are stored 1024 bytes below.
     */
    #define L_PTE_VALID		(_AT(pteval_t, 1) << 0)		/* Valid */
    #define L_PTE_PRESENT		(_AT(pteval_t, 1) << 0)
    #define L_PTE_YOUNG		(_AT(pteval_t, 1) << 1)
    #define L_PTE_DIRTY		(_AT(pteval_t, 1) << 6)
    #define L_PTE_RDONLY		(_AT(pteval_t, 1) << 7)
    #define L_PTE_USER		(_AT(pteval_t, 1) << 8)
    #define L_PTE_XN		(_AT(pteval_t, 1) << 9)
    #define L_PTE_SHARED		(_AT(pteval_t, 1) << 10)	/* shared(v6), coherent(xsc3) */
    #define L_PTE_NONE		(_AT(pteval_t, 1) << 11)
    
    /*
     * These are the memory types, defined to be compatible with
     * pre-ARMv6 CPUs cacheable and bufferable bits: n/a,n/a,C,B
     * ARMv6+ without TEX remapping, they are a table index.
     * ARMv6+ with TEX remapping, they correspond to n/a,TEX(0),C,B
     *
     * MT type		Pre-ARMv6	ARMv6+ type / cacheable status
     * UNCACHED		Uncached	Strongly ordered
     * BUFFERABLE		Bufferable	Normal memory / non-cacheable
     * WRITETHROUGH		Writethrough	Normal memory / write through
     * WRITEBACK		Writeback	Normal memory / write back, read alloc
     * MINICACHE		Minicache	N/A
     * WRITEALLOC		Writeback	Normal memory / write back, write alloc
     * DEV_SHARED		Uncached	Device memory (shared)
     * DEV_NONSHARED	Uncached	Device memory (non-shared)
     * DEV_WC		Bufferable	Normal memory / non-cacheable
     * DEV_CACHED		Writeback	Normal memory / write back, read alloc
     * VECTORS		Variable	Normal memory / variable
     *
     * All normal memory mappings have the following properties:
     * - reads can be repeated with no side effects
     * - repeated reads return the last value written
     * - reads can fetch additional locations without side effects
     * - writes can be repeated (in certain cases) with no side effects
     * - writes can be merged before accessing the target
     * - unaligned accesses can be supported
     *
     * All device mappings have the following properties:
     * - no access speculation
     * - no repetition (eg, on return from an exception)
     * - number, order and size of accesses are maintained
     * - unaligned accesses are "unpredictable"
     */
    #define L_PTE_MT_UNCACHED	(_AT(pteval_t, 0x00) << 2)	/* 0000 */
    #define L_PTE_MT_BUFFERABLE	(_AT(pteval_t, 0x01) << 2)	/* 0001 */
    #define L_PTE_MT_WRITETHROUGH	(_AT(pteval_t, 0x02) << 2)	/* 0010 */
    #define L_PTE_MT_WRITEBACK	(_AT(pteval_t, 0x03) << 2)	/* 0011 */
    #define L_PTE_MT_MINICACHE	(_AT(pteval_t, 0x06) << 2)	/* 0110 (sa1100, xscale) */
    #define L_PTE_MT_WRITEALLOC	(_AT(pteval_t, 0x07) << 2)	/* 0111 */
    #define L_PTE_MT_DEV_SHARED	(_AT(pteval_t, 0x04) << 2)	/* 0100 */
    #define L_PTE_MT_DEV_NONSHARED	(_AT(pteval_t, 0x0c) << 2)	/* 1100 */
    #define L_PTE_MT_DEV_WC		(_AT(pteval_t, 0x09) << 2)	/* 1001 */
    #define L_PTE_MT_DEV_CACHED	(_AT(pteval_t, 0x0b) << 2)	/* 1011 */
    #define L_PTE_MT_VECTORS	(_AT(pteval_t, 0x0f) << 2)	/* 1111 */
    #define L_PTE_MT_MASK		(_AT(pteval_t, 0x0f) << 2)
    

      内核对应的宏,位于pgtable-2level-hwdef.h

    /*
     * + Level 2 descriptor (PTE)
     *   - common
     */
    #define PTE_TYPE_MASK		(_AT(pteval_t, 3) << 0)
    #define PTE_TYPE_FAULT		(_AT(pteval_t, 0) << 0)
    #define PTE_TYPE_LARGE		(_AT(pteval_t, 1) << 0)
    #define PTE_TYPE_SMALL		(_AT(pteval_t, 2) << 0)
    #define PTE_TYPE_EXT		(_AT(pteval_t, 3) << 0)		/* v5 */
    #define PTE_BUFFERABLE		(_AT(pteval_t, 1) << 2)
    #define PTE_CACHEABLE		(_AT(pteval_t, 1) << 3)
    
    /*
     *   - extended small page/tiny page
     */
    #define PTE_EXT_XN		(_AT(pteval_t, 1) << 0)		/* v6 */
    #define PTE_EXT_AP_MASK		(_AT(pteval_t, 3) << 4)
    #define PTE_EXT_AP0		(_AT(pteval_t, 1) << 4)
    #define PTE_EXT_AP1		(_AT(pteval_t, 2) << 4)
    #define PTE_EXT_AP_UNO_SRO	(_AT(pteval_t, 0) << 4)
    #define PTE_EXT_AP_UNO_SRW	(PTE_EXT_AP0)
    #define PTE_EXT_AP_URO_SRW	(PTE_EXT_AP1)
    #define PTE_EXT_AP_URW_SRW	(PTE_EXT_AP1|PTE_EXT_AP0)
    #define PTE_EXT_TEX(x)		(_AT(pteval_t, (x)) << 6)	/* v5 */
    #define PTE_EXT_APX		(_AT(pteval_t, 1) << 9)		/* v6 */
    #define PTE_EXT_COHERENT	(_AT(pteval_t, 1) << 9)		/* XScale3 */
    #define PTE_EXT_SHARED		(_AT(pteval_t, 1) << 10)	/* v6 */
    #define PTE_EXT_NG		(_AT(pteval_t, 1) << 11)	/* v6 */
    

    PTE设置函数

      ARMv7架构,set_pte_ext位于arch/arm/mm/proc-v7-2level.S中,代码实现如下:

    #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)
    

    该接口有三个参数,Linux版本pte地址,要写入页表的属性,以及额外的属性。

    ENTRY(cpu_v7_set_pte_ext)
    #ifdef CONFIG_MMU
    	str	r1, [r0]			@ linux version  @直接把pte属性写入Linux版本页表
    									
    									
    	bic	r3, r1, #0x000003f0			@r1值清除bit4~bit9存储到r3,r3作为临时变量存储硬件pte属性。
    	bic	r3, r3, #PTE_TYPE_MASK		@清除r3 bit0、bit1,没有清除bit3、bit2是因为cache、buffer这两位,hw和linux保持一致。
    	orr	r3, r3, r2					@r3=r3|r2,r2是额外属性
    	orr	r3, r3, #PTE_EXT_AP0 | 2	@设置hw bit4(AP0bit),设置hw bit1(根据上图,可以得知small page下bit1必须等于1)
    
    	tst	r1, #1 << 4                 @判断r1传入的页表属性,是否设置了bit4(tex位)
    	orrne	r3, r3, #PTE_EXT_TEX(1)	@如果linux属性设置了tex位,则硬件版本也要置位tex(hw bit6~8),两者bit偏移位置不一样。
    
    	eor	r1, r1, #L_PTE_DIRTY				@翻转r1 DIRTY位(bit6)
    	tst	r1, #L_PTE_RDONLY | L_PTE_DIRTY		@判断是否设置了只读。+
    	orrne	r3, r3, #PTE_EXT_APX			@设置硬件版本hw bit9(APX值(置1只读))
    
    	tst	r1, #L_PTE_USER						@判断传入属性是否设置L_PTE_USR
    	orrne	r3, r3, #PTE_EXT_AP1			@设置硬件版本hw bit5(AP1bit)
    
    	tst	r1, #L_PTE_XN						@判断传入属性是否设置XN(不可执行)
    	orrne	r3, r3, #PTE_EXT_XN				@设置硬件版本hw bit0(不可执行位)
    
    	tst	r1, #L_PTE_YOUNG
    	tstne	r1, #L_PTE_VALID
    	eorne	r1, r1, #L_PTE_NONE
    	tstne	r1, #L_PTE_NONE
    	moveq	r3, #0
    
     ARM(	str	r3, [r0, #2048]! )
     THUMB(	add	r0, r0, #2048 )
     THUMB(	str	r3, [r0] )
    	ALT_SMP(W(nop))
    	ALT_UP (mcr	p15, 0, r0, c7, c10, 1)		@ flush_pte
    #endif
    	bx	lr
    ENDPROC(cpu_v7_set_pte_ext)
    

      根据汇编代码,可以看到清除了linux页表属性的bit0、bit1、bit4~bit9。也即bit2、bit3 linux页表和硬件页表一致。其余bit不一样。基本注释都已在上面写了。
      DIRTY页模拟,创建映射时,设置硬件页表是只读的。当往页面写入时,会触发写权限缺页中断。(虽然Linux版本页面表标记了可写权限,但是ARM硬件表,没有写入权限。)在缺页中断handle_pte_fault中会在该页的Linux版本PTE中标记为“dirty”。

    tst	r1, #L_PTE_YOUNG
    tstne	r1, #L_PTE_VALID
    eorne	r1, r1, #L_PTE_NONE
    tstne	r1, #L_PTE_NONE
    moveq	r3, #0
    

      这部分代码,可以等同于如下:

    tst r1, @L_PTE_YOUNG
    tstne r1, #L_PTE_PRESENT
    moveq r3, #0
    

      如果没有设置L_PTE_YOUNG并且L_PTE_PRESENT置位,那么保持Linux版本页表不变,把ARM硬件版本页表项内容清0。

    展开全文
  • linux 页表

    千次阅读 2013-04-05 19:47:14
     简单来说,讨论linux页表就是讨论linux进程的的页表:linux页表的创建与更新都包含于进程的创建与更新中。当前的linux内核采用的是写时复制方法,在创建一个linux进程时,完全复制父进程的页表,并且将父子进程的...

    原文网址:

    http://blog.chinaunix.net/uid-26817832-id-3146395.html


     简单来说,讨论linux页表就是讨论linux进程的的页表:linux页表的创建与更新都包含于进程的创建与更新中。当前的linux内核采用的是写时复制方法,在创建一个linux进程时,完全复制父进程的页表,并且将父子进程的页表均置为写保护(即写地址的时候会产生缺页异常等)。那么父子进程谁向地址空间写数据时,产生缺页异常,分配新的页,并将两个页均置为可写,按照这种方式父子进程的地址空间渐渐变得不同(也即页表变得不同)。

    按照上面的分析, 只需要讨论第一个进程页表初始化,进程创建时页表的拷贝,以及缺页异常时页表的更新即可。
    1.init_task进程页表的初始化
    init_task的地址空间是init_mm, init_mm在内核初始化的时候就赋值给了current->active_mm. init_mm的初始化页表是swapper_pg_dir,在mips架构中swapper_pg_dir初始化在函数pagetable_init中,初始化关系是
    swapper_pg_dir -> invalide_pmd_table -> invalide_pte_table 或
    swapper_pg_dir -> invalide_pte_table.
    即在init_mm中,页表指向的全部是invalide_pte_table。

    2.创建进程时页表的拷贝
    进程创建一般调用的是do_fork函数,按照如下调用关系:
    do_fork->copy_process->copy_mm->dup_mm->dup_mmap->copy_page_range
    找到copy_page_range函数,这个函数便是负责页表的拷贝,函数核心代码如下:
    874 do {
    875 next = pgd_addr_end(addr, end);
    876 if (pgd_none_or_clear_bad(src_pgd))
    877 continue;
    878 if (unlikely(copy_pud_range(dst_mm, src_mm, dst_pgd, src_pgd,
    879 vma, addr, next))) {
    880 ret = -ENOMEM;
    881 break;
    882 }
    883 } while (dst_pgd++, src_pgd++, addr = next, addr != end);
    copy_pud_range便是拷贝pud表,copy_pud_range调用copy_pmd_range, copy_pmd_range调用copy_pte_range,以此完成对三级页表的复制。需要注意的是在copy_pte_range调用的copy_one_pte中有如下代码:
    694 if (is_cow_mapping(vm_flags)) {
    695 ptep_set_wrprotect(src_mm, addr, src_pte);
    696 pte = pte_wrprotect(pte);
    697 }
    这里便是判断如果采用的是写时复制,便将父子页均置为写保护,即会产生如下所示的缺页异常。

    3. 缺页异常时页表的更新
    由页表的初始化可以看到,init_mm的页表全指向无效页表,然而普通的进程中不可能页表均指向无效项,因此肯定拥有一个不断扩充页表的机制,这个机制是通过缺页异常实现的。
    以mips为例,mips的缺页异常最终会调用do_page_fault,do_page_fault调用handle_mm_fault,handle_mm_fault是公共代码,一般所有的缺页异常均会调用handle_mm_fault的核心代码如下:
    3217 pud = pud_alloc(mm, pgd, address);
    3218 if (!pud)
    3219 return VM_FAULT_OOM;
    3220 pmd = pmd_alloc(mm, pud, address);
    3221 if (!pmd)
    3222 return VM_FAULT_OOM;
    3223 pte = pte_alloc_map(mm, pmd, address);
    3224 if (!pte)
    3225 return VM_FAULT_OOM;
    其中 pud_alloc代码如下:
    1056 static inline pud_t *pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address)
    1057 {
    1058 return (unlikely(pgd_none(*pgd)) && __pud_alloc(mm, pgd, address))?
    1059 NULL: pud_offset(pgd, address);
    1060 }
    其中 pgd_none用于判断pgd是否为invalide,如果是可调用 __pud_alloc,如果不是获得其地址继续查。
    pmd_alloc函数和 pte_alloc_map函数类似。

    因此可以看出,在缺页异常中,会按照地址一次查三张页表,如果页表为invalide,比如invalide_pmd_table或invalide_pte_table,则会分配一个新的页表项取代invalide的页表项。这便是页表扩充的机制。

    需要注意的是handle_mm_fault最终会调用handle_pte_fault,在handle_pte_fault函数中有如下代码:
    3171 if (flags & FAULT_FLAG_WRITE) {
    3172 if (!pte_write(entry))
    3173 return do_wp_page(mm, vma, address,
    3174 pte, pmd, ptl, entry);
    3175 entry = pte_mkdirty(entry);
    3176 }
    即在缺页异常中如果遇到写保护会调用 do_wp_page,这里面会处理上面所说的写时复制中父子进程区分的问题。

    如上三个部分便是linux页表的大体处理框架

    自己的理解:
    1、swapper_pg_dir : Global Page Directory (全局页目录,即最顶层页目录,PGD) 的地址
    2、几个名词:页全局目录(PGD);页上级目录(PUD);页中级目录(PMD);页表(PTE)
    3、mips 虚拟地址内存空间映射如下:
    0x0000 0000 ~ 0x7fff ffff
    用户级空间,2GB,要经MMU(TLB)地址翻译。kuseg。可以控制要不要经过缓冲。

    0x8000 0000 ~ 0x9fff ffff
    kseg0. 这块区域为操作系统内核所占的区域,共512M。使用时,不经过地址翻译,将最高位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。但要经过缓冲区过渡。

    0xa000 0000 ~ 0xbfff ffff
    kseg1. 这块区域为系统初始化所占区域,共512M。使用时,不经过地址翻译,也不经过缓冲区。将最高3位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。

    0xc000 0000 ~ 0xffff ffff
    kseg2. 这块区域也为内核级区域。要经过地址翻译。可以控制要不要经过缓冲。

    核代码运行在0x8000000000 ~ x9fffffff的地址空间范围,共0.5G。对内核而言,显然它应该具备访问整个物理RAM的功能,这里即存在如下问题:
    • 内核应首先建立自己的页表,以便内核本身能寻址物理RAM(mips 处理器不存在这个问题,0x8000000000 ~ x9fffffff 转换到0x0000000000 ~ x1fffffff的物理空间是由处理器内部的地址译码部件自动完成的)
    • 内核本身的地址空间只有0.5G,对于RAM大于0.5G时,内核如何寻址?
    4、关于第一个问题的进一步解释
    在内核arch/mips/kernel/vmlinux.lds
    OUTPUT_ARCH(mips)
    ENTRY(kernel_entry)
    PHDRS {
    text PT_LOAD FLAGS(7);
    note PT_NOTE FLAGS(4);
    }
    jiffies = jiffies_64;
    SECTIONS
    {
    . = 0xffffffff80200000;

    _text = .;
    .text : {
    . = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.text.unlikely)
    . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
    . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
    . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;

    *(.text.*)
    *(.fixup)
    *(.gnu.warning)
    ....
    这个文件最终会以参数 -Xlinker --script -Xlinker vmlinux.lds 的形式传给 gcc,并最终传给链接器 ld 来控制其行为。ld 会将 .text 节的地址链接到 0xFFFFFFFF80200000 处。而pmon会将内核elf文件移到物理地址0x00200000处,代码调用关键如下:
    load命令 -> load_elf -> bootread

    pc 寄存器中显示的是虚拟地址,cpu根据pc寄存器的值来取指,取值要访存,经过地址译码部件时,自动将0x8000000000 ~ x9fffffff 转换为0x0000000000 ~ x1fffffff(物理地址)。

    5、第二个问题的处理

    在前面的讨论中,一直存在一个为难题,即内核本身的地址空间只有0.5G(0x800000000-0x9fffffff),对于RAM大于0.5G时,内核如何寻址? 因此显然RAM等于0.5G是一个分界点;另外,对32位的机器而言,通产其线性地址空间为0~4G范围,当RAM大于4G时如何寻址?因此4G又是另一个分界点。

    Linux的实现采用固定,永久映射+动态映射的方式来解决上述问题。
    X86具体做法是,内核建立0~896MB的内核映射,即将0xc0000000-0xf0000000映射到物理内存前896MB;而对后128MB的线性地址空间(0xf000000~0xffffffff)采用动态映射方法,即根据需要映射到内存896MB以后的任意地址。具体实现机制请参考<<深入理解linux内核>>。

    6、pagetable_init 主要完成固定映射和永久映射,将动态映射的页表无效,动态映射(一般是用户态的地址,进程相关的)如上面台运方所说,需要进一步看一下pagetable_init的代码,看看mips固定映射以及永久映射的具体实现。

    7、线性地址空间被分成固定长度为单位的组,称为页,页内部连续的线性地址映射到连续的物理地址,这样,内核可以指定一个页的物理地址和其存取权限,而不用指定页所包含的全部线性地址的存取权限。注意:页只是一个数据块,可以存放在任何页框或磁盘中。X86处理器中有一个分页单元完成线性地址到物理地址的解释,当然在启用分页单元之前,先由内核对页表进行适当的初始化。

    8、每个活动的进程拥有一个页表集和全局页目录,必须分配给它一个全局页目录,不过没有必要为进程的所有页表分配RAM,只有当进程实际需要一个页表时才给该页表分配RAM会更又效率。

    9、进程共享的是虚拟地址空间。

    展开全文
  • Linux页表

    2016-12-15 01:32:28
     虽然应用程序操作的...地址的转换工作需要通过查询页表才能完成,地址转换需要将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表项则指向下一级别的页表或指向最终的物理页面。  Linux中使用三...

            虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。所以当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成,地址转换需要将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表项则指向下一级别的页表或指向最终的物理页面。

           Linux中使用三级页表完成地址转换。利用多级页表能够节约地址转换需要占用的存放空间。如果利用三级页表转换地址,即使是64位机器,占用的空间也很有限。但是如果使用静态数组实现页表,那么即便在32位机器上,改数组也将占用巨大的存放空间。Linux对所有的体系结构都使用三级页表管理内存。

           顶级页表是页全局目录(PGD),它包含了一个pgd_t类型数组,多数体系结构中pgd_t类型等同于无符号长整形类型。PGD中的表项指向二级页目录中的表项:PMD。

            二级页表是中间页目录(PMD),它是个pmd_t类型数组,其中表项指向PTE中的表项。

            最后一级的页表简称页表,其中包含了pte_t类型页表项,该页表项指向物理页面。

            每个进程都有自己的页表,内存描述符的pdg域指向的就是进程的全局目录。操作和检索页表时必须使用page_table_lock锁,该锁在相应的进程的内存描述符中,以防止竞争条件。


            由于几乎每次对虚拟内存中的页面访问都必须先解析,从而得到物理内存中的对应地址,所以页表操作的性能非常关键。但是不幸的是,搜索内存中的物理地址速度很有限,因此为了加快搜索,多数体系结构都实现了一个翻译后缓冲器(translate lookaside buffer ,TLB)。TLB作为一个将虚拟地址映射到物理地址的硬件缓存,当请求访问一个虚拟地址时,处理器将首先检查TLB中是否缓存了该虚拟地址到物理地址的映射,如果在缓存中直接命中,物理地址立刻返回,否则就需要再通过页表搜索需要的物理地址。

    展开全文
  • Linux页表初始化

    2016-07-27 09:43:28
    Linux页表初始化
  • arm linux 页表创建

    千次阅读 2017-07-03 22:07:44
    本文对arm linux页表创建函数进行说明。在http://blog.csdn.net/flaoter/article/details/73381695中对MMU使能之前的临时页表进行了说明,此文是对kernel中正式使用的页表创建过程进行说明。 arm linux使用两级页表...
  • 在看内核代码初始化arm页表的时候,发现linux内核做了一些特殊处理,引入了硬件页表、linux页表的概念,这篇文章描述为何需要这样处理以及具体的实现方式。 原因: linux中ARM32采用2级页表映射方式,MMU映射过程...
  • linux页表创建与更新

    2016-05-15 12:02:41
    简单来说,讨论linux页表就是讨论linux进程的的页表:linux页表的创建与更新都包含于进程的创建与更新中。当前的linux内核采用的是写时复制方法,在创建一个linux进程时,完全复制父进程的页表,并且将父子进程的...
  • linux页表问题

    千次阅读 2010-02-09 18:19:00
    1.内核页表问题 kmalloc与kmem_cache_alloc之后的页表 实际上前者是后者实现的,而且物理地址连续内核在执行kmalloc的时候,并没有发现有设置页表的行为,实际上是在系统启动的时候在 kernel_physical_mapping_init...
  • Linux页表机制初始化

    万次阅读 2012-05-09 17:18:26
    Linux启动并建立一套完整的页表机制要经过以下几个步骤: 1.临时内核页表的初始化(setup_32.s) 2.启动分页机制(head_32.s) 3.建立低端内存和高端内存固定映射区的页表( init_memory_mapping()) 4.建立高端内存...
  • linux页表机制管理初始化

    千次阅读 2012-01-04 15:25:05
    linux页表机制的建立分为两个阶段,第一个阶段在启动初始化时,调用汇编代码实现临时的分页;第二阶段在内核初始化时建立完整的完整的页表机制。对于物理地址扩展(PAE)分页机制中,intel通过在她得处理器上把管脚数...
  • https://www.cnblogs.com/LoyenWang/p/11406693.html (一)ARMv8 MMU及Linux页表映射
  • 背景 Read the fucking source code! --By 鲁迅 ...要想理解好Linux页表映射,MMU的机制是需要去熟悉的,因此将这两个模块放到一起介绍。 关于ARMv8 MMU的相关内容,主要参考文档:《ARM Cortex-A Series
  • linux页表pgd的含义

    千次阅读 2015-07-18 15:23:40
    Linux系统中每个进程对应用户空间的pgd是不一样的,但是linux内核 的pgd是一样的。 当创建一个新的进程时,都要为新进程创建一个新的页面目录PGD,并从内核的页面目录swapper_pg_dir中复制内核区间页面目录项至...
  • 厘清了什么是分段, 什么是分页,搞清楚了linux的三种地址,即逻辑地址、线性地址、物理地址, 也梳理了x86 linux的启动过程以及至关重要的页表建立的过程,现在再来看一看ARM linux页表的建立过程,毕竟X86 和ARM ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,287
精华内容 12,514
关键字:

linux页表的作用

linux 订阅