Linux内核剖析 之 内存寻址(三)

来源:互联网 发布:淘宝节点考试在哪里 编辑:程序博客网 时间:2024/04/29 20:24

//接前一篇博客:

本节主要讲述Linux中的分页机制,注意对比Linux和80x86下分页机制的不同。

5Linux中的分页


                 图:Linux分页模式(四级页表)

       Linux的进程处理很大程度上依赖于分页,每个进程都有自己的页全局目录和自己的页表集。

5.1、线性地址字段:

      下列宏简化了页表处理。(80x86

      PAGE_SHIFT

      指定Offset字段的位数;当用于80x86处理器时,其值为12

      PMD_SHIFT

      指定线性地址的Offset字段和Table字段的总位数;即页中间目录项可以映射的区域大小的对数。

      PUD_SHIFT

      确定页上级目录项所能映射的区域大小的对数。

      PGDIR_SHIFT

      确定页全局目录项所能映射的区域大小的对数。

      PTRS_PER_PTEPTRS_PER_PMDPTRS_PER_PUDPTRS_PER_PGD

      用于计算页表、页中间目录、页上级目录和页全局目录表中表项的个数。

      PAE被禁用时,它们产生的值分别为102411102432位系统,页上级目录(PUD)、页中间目录(PMD)置0,从根本上取消了页上级目录和页中间目录字段)。

      PAE被激活时,产生的值分别为5125121432位系统使用三级页表,Linux的页全局目录对应于80x86的页目录指针表(PDPT),取消了页上级目录,页中间目录对应页目录,页表对应80x86的页表)。

====》注意区分Linux的分页机制和80x86的分页机制的不同。

5.2、页表处理(宏)

      #页表项格式转化

      pte_tpmd_tpud_tpgd_t分别描述页表项、页中间目录项、页上级目录项和页全局目录项的格式。pgprot_t表示与一个单独表项相关的保护标志。

 

      #读页标志的函数

      #设置页标志的函数

      #对页表项操作的宏

      #页分配函数

5.3、物理内存布局:

      在初始化阶段,内核必须建立一个物理地址映射来指定哪些物理地址范围对内核可用而哪些物理地址范围对内核不可用。

      内核将下列页框标记为保留:

            #在不可用的物理地址范围内的页框;

            #含有内核代码和已初始化的数据结构的页框。

      一般来说,Linux内核安装在RAM从物理地址0x00100000开始的地方,即从第二个MB开始。

      为什么内核没有安装在RAM的第一个MB开始的地方?因为PC的体系结构,有几个独特的地方必须考虑到。如:

            #页框0BIOS使用,存放加电自检检查到的系统硬件配置信息;

            #物理地址从0x000a00000x000fffff的范围通常留给BIOS例程;

            #第一个MB内的页框可能由特定计算机模型保留。

      Linux2.6的前768个页框(3MB):

 

5.4、进程页表:

      每一个进程都有它自己的页全局目录和页表集,当发生进程切换时,cr3的内容被保存在前一个执行进程的task_struct中,(task_struct->mm->pgd

      将下一个进程的pgd地址装入cr3寄存器。

      进程的线性地址空间分为两部分

            0-3G    用户态与内核态都可寻址

            3G-4G 只有内核态才能寻址

      进程的页全局目录的第一部分表项映射的线性地址小于3G

      剩余的表项对于所有进程都是相同的,等于内核页表中的相应表项。

 

5.5、内核页表:

      内核维持着一组自己使用的页表,驻留在主内核页全局目录中。在系统初始化时建立。

      #内核创建一个有限的地址空间,将内核装入RAM中和对其初始化的核心数据结构进行存储;

      #内核充分利用剩余的RAM并适当地建立分页表。

      (1)、临时内核页表

      临时页全局目录是在内核编译过程中静态初始化的,而临时页表是由startup_32()汇编语言函数初始化的。

      临时页全局目录放在swapper_pg_dir变量中。临时页表在pg0变量处开始存放,紧接在内核未初始化的数据段后面。

      (2)、当RAM小于896MB时的最终内核页表

      Swapper_pg_dir页全局目录有如下代码初始化:

     pagetable_init()函数====>>>

static void __init pagetable_init (void){unsigned long vaddr;pgd_t *pgd_base = swapper_pg_dir;#ifdef CONFIG_X86_PAEint i;/* Init entries of the first-level page table to the zero page */for (i = 0; i < PTRS_PER_PGD; i++)set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT));#endif/* Enable PSE if available */if (cpu_has_pse) {set_in_cr4(X86_CR4_PSE);}/* Enable PGE if available */if (cpu_has_pge) {set_in_cr4(X86_CR4_PGE);__PAGE_KERNEL |= _PAGE_GLOBAL;__PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;}kernel_physical_mapping_init(pgd_base); //核心语句,具体实现页初始化remap_numa_kva();/* * Fixed mappings, only the page table structure has to be * created - mappings will be set by set_fixmap(): */vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;page_table_range_init(vaddr, 0, pgd_base);permanent_kmaps_init(pgd_base);#ifdef CONFIG_X86_PAE/* * Add low memory identity-mappings - SMP needs it when * starting up on an AP from real-mode. In the non-PAE * case we already have these mappings through head.S. * All user-space mappings are explicitly cleared after * SMP startup. */pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];#endif}

       kernel_physical_mapping_init()函数====>>>关键在于中间的for循环实现!

static void __init kernel_physical_mapping_init(pgd_t *pgd_base){unsigned long pfn;pgd_t *pgd;pmd_t *pmd;pte_t *pte;int pgd_idx, pmd_idx, pte_ofs;pgd_idx = pgd_index(PAGE_OFFSET);pgd = pgd_base + pgd_idx;pfn = 0;for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {pmd = one_md_table_init(pgd);if (pfn >= max_low_pfn)continue;for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET;/* Map with big pages if possible, otherwise create normal page tables. */if (cpu_has_pse) {unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;if (is_kernel_text(address) || is_kernel_text(address2))set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));elseset_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));pfn += PTRS_PER_PTE;} else {pte = one_page_table_init(pmd);for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {if (is_kernel_text(address))set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));elseset_pte(pte, pfn_pte(pfn, PAGE_KERNEL));}}}}}
      (3)、当RAM大小在896MB4096MB之间时的最终内核页表

      在这种情况下,并不把所有RAM全部映射到内核地址空间。Linux把一个具有896MBRAM窗口映射到内核线性地址空间。如果一个程序需要对现有RAM的其余部分寻址,必须把某些其他的线性地址间接映射到所需的RAM(动态重映射)。

      (4)、当RAM大于4096MB时的最终内核页表

      使用三级分页模型。


6、处理硬件高速缓存和TLB

      硬件高速缓存的同步,由处理器自动完成。

      TLB的同步,由内核完成,因为线性地址到物理地址的映射是否有效,由内核决定。

      独立于系统的使TLB表项无效的方法:

      

      在一个处理器上运行的函数发送处理器间中断,给其他CPU,强制它们执行适当的函数刷新TLB


0 0
原创粉丝点击