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

简介:

//接前一篇博客:

本节主要讲述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_PAE
	int 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));
				else
					set_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));
						else
							set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));
				}
			}
		}
	}
}
      (3) 、当RAM大小在896MB4096MB之间时的最终内核页表

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

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

      使用三级分页模型。


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

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

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

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

      

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


目录
相关文章
|
6天前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
33 15
|
1月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
1月前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
1月前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
1月前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
1月前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
37 3
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
381 1
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
2月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
27 3

热门文章

最新文章