进程页表与内核页表:页表的初始化

摘要:linux刚刚加电启动时,如何从实模式进入保护模式?启动分页机制的前提是什么?如何保证分页机制之前和之后通过实地址和虚拟地址都能访问到同一个物理地址呢?内核页表是如何进行初始化的?用户进程不能访问内核的数据是在初始化的哪个阶段决定的?这些内容,都牵扯到linu的进程页表和内核页表,以及内核页表的初始化。本文也主要为你解答这些疑问.


本文来源:进程页表和内核页表:页表的初始化


1.进程页表:

关键数据结构:

PAGE_OFFSET: 0xc000000


进程地址空间以0xc0000000(由宏PAGE_OFFSET定义)分割成两个部分,进程运行在用户态,产生的地址小于0xc0000000;进程运行在核心态,产生的地址大于0xc0000000.但是,在某些情况下,内核为了访问数据必须访问用户态线性地址空间。


进程地址空间,其中的内核态部分对于所有的进程都是一样的,等于主内核页全局目录的相应表项。


2.内核页表


内核维护着自己的页表,驻留在所谓的主内核页全局目录中。我们将在此处解释:内核如何初始化自己的页表。

1)第一阶段:内核镜像刚刚装入内存,CPU处于实模式,分页功能尚未开启,内核创建一个有限的地址空间,128K,仅仅将内核装入RAM并初始化核心数据。

2)第二阶段:内核充分利用剩余的RAM建立页表,下面,我们将详细讨论这个页表的建立过程。


2.1临时内核页表



关键数据结构:

swapper_pg_dir:临时页全局目录对应的虚拟地址。

pg0:第一个页所在的物理地址


临时页全局目录在编译内核过程中静态初始化,而临时页全局目录存放在swapper_pg_dir之中。临时页表从pg0变量处开始存放。这里,我们假设内核使用的段,临时页表和128KB的内存初始化数据可以存放在前8M的RAM之中。为了映射这8M,我们需要用到2个页表。


开启分页的首要任务是确保实模式和保护模式下都能对前8M进行寻址(参考其中有关控制寄存器CR3的部分)。就是说,从0x0000000到0x007fffff的线性地址,从0xc0000000到0xc07fffff均可映射到物理地址范围:0x00000000到0x007fffff。

是startup_32()来初始化的。它的等价代码(这段代码见于2.4.0内核,2.6内核以后不是这样)如下:


 98         movl $swapper_pg_dir-__PAGE_OFFSET,%eax
 99         movl %eax,%cr3          /* set the page table pointer.. */
100         movl %cr0,%eax
101         orl $0x80000000,%eax
102         movl %eax,%cr0          /* ..and set paging (PG) bit */

其中,swapper_pg_dir是一个数组变量的名称;内核通过将swapper_pg_dir的所有项都填充为0.除了0、1,ox300(十进制768),0x301(十进制769)除外。这四项按照下列方式进行初始化:


* 0和0x300设置位pg0的物理地址,1和0x301设置成pg1的地址

* 四项的present、R/W,U/S置位

* 四项的accessed、dirty、pcd和pagesize位置零


2.2RAM小于896M时候的最终内核页表


关键数据结构:


关键函数:

宏__pa和__va分别进行相应区域的物理地址和线性地址之间的转换:位于内核空间的转换


由内核页表所提供的最终映射必须把从0xc0000000开始的线性地址映射到从0开始的物理地址。其中宏__pa和__va分别进行相应区域的物理地址和线性地址之间的转换。


主内核全局目仍然在swapee_page_dir变量中,它由paging_init()函数进行初始化:

444 void __init paging_init(void)
445 {
446 pagetable_init();
447 
448 __asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_pg_dir)));
449 
450 #if CONFIG_X86_PAE
451 /*
452 * We will bail out later - printk doesnt work right now so
453 * the user would just see a hanging kernel.
454 */
455 if (cpu_has_pae)
456 set_in_cr4(X86_CR4_PAE);
457 #endif 
458 
459 __flush_tlb_all();
460 
461 #ifdef CONFIG_HIGHMEM
462 kmap_init();
463 #endif
464 {
465 unsigned long zones_size[MAX_NR_ZONES] = {0, 0, 0};
466 unsigned int max_dma, high, low;
467 
468 max_dma = virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT;
469 low = max_low_pfn;
470 high = highend_pfn;
471 
472 if (low < max_dma)
473 zones_size[ZONE_DMA] = low;
474 else {
475 zones_size[ZONE_DMA] = max_dma;
476 zones_size[ZONE_NORMAL] = low - max_dma;
477 #ifdef CONFIG_HIGHMEM
478 zones_size[ZONE_HIGHMEM] = high - low;
479 #endif
480 }
481 free_area_init(zones_size);
482 }
483 return;
484 }

函数执行过程如下:

1)使用pagetable_init()建立页表项

2)将swapper_pg_dir的物理地址写入cr3

3)如果CPU编译内核的时候支持PAE,则将CR4控制寄存器的PAE置位

4)调用__flush_tlb_all()使得所有的TLB无效


pagetable_init()执行的操作依赖于RAM容量和CPU模型,对全局目录的初始化代码等价如下:

314 static void __init pagetable_init (void)
315 {
316         unsigned long vaddr, end;
317         pgd_t *pgd, *pgd_base;
318         int i, j, k;
319         pmd_t *pmd;
320         pte_t *pte;
321 
322         /*
323          * This can be zero as well - no problem, in that case we exit
324          * the loops anyway due to the PTRS_PER_* conditions.
325          */
326         end = (unsigned long)__va(max_low_pfn*PAGE_SIZE);
327 
328         pgd_base = swapper_pg_dir;
329 #if CONFIG_X86_PAE
330         for (i = 0; i < PTRS_PER_PGD; i++) {
331                 pgd = pgd_base + i;
332                 __pgd_clear(pgd);
333         }
334 #endif
335         i = __pgd_offset(PAGE_OFFSET);
336         pgd = pgd_base + i;
337 
338         for (; i < PTRS_PER_PGD; pgd++, i++) {
339                 vaddr = i*PGDIR_SIZE;
340                 if (end && (vaddr >= end))
341                         break;
342 #if CONFIG_X86_PAE
343                 pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
344                 set_pgd(pgd, __pgd(__pa(pmd) + 0x1));
345 #else
346                 pmd = (pmd_t *)pgd;
347 #endif
348                 if (pmd != pmd_offset(pgd, 0))
349                         BUG();
350                 for (j = 0; j < PTRS_PER_PMD; pmd++, j++) {
351                         vaddr = i*PGDIR_SIZE + j*PMD_SIZE;
352                         if (end && (vaddr >= end))
353                                 break;
354                         if (cpu_has_pse) {
355                                 unsigned long __pe;
356 
357                                 set_in_cr4(X86_CR4_PSE);
358                                 boot_cpu_data.wp_works_ok = 1;
359                                 __pe = _KERNPG_TABLE + _PAGE_PSE + __pa(vaddr);
360                                 /* Make it "global" too if supported */
361                                 if (cpu_has_pge) {
362                                         set_in_cr4(X86_CR4_PGE);
363                                         __pe += _PAGE_GLOBAL;
364                                 }
365                                 set_pmd(pmd, __pmd(__pe));
366                                 continue;
367                         }
368 
369                         pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
370                         set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte)));
371 
372                         if (pte != pte_offset(pmd, 0))
373                                 BUG();
374 
375                         for (k = 0; k < PTRS_PER_PTE; pte++, k++) {
376                                 vaddr = i*PGDIR_SIZE + j*PMD_SIZE + k*PAGE_SIZE;
377                                 if (end && (vaddr >= end))
378                                         break;
379                                 *pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL);
380                         }
381                 }
382         }
383 
384         /*
385          * Fixed mappings, only the page table structure has to be
386          * created - mappings will be set by set_fixmap():
387          */
388         vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
389         fixrange_init(vaddr, 0, pgd_base);
390 
391 #if CONFIG_HIGHMEM
392         /*
393          * Permanent kmaps:
394          */
395         vaddr = PKMAP_BASE;
396         fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);
397 
398         pgd = swapper_pg_dir + __pgd_offset(vaddr);
399         pmd = pmd_offset(pgd, vaddr);
400         pte = pte_offset(pmd, vaddr);
401         pkmap_page_table = pte;
402 #endif
403 
404 #if CONFIG_X86_PAE
405         /*
406          * Add low memory identity-mappings - SMP needs it when
407          * starting up on an AP from real-mode. In the non-PAE
408          * case we already have these mappings through head.S.
409          * All user-space mappings are explicitly cleared after
410          * SMP startup.
411          */
412         pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
413 #endif
414 }


由startup_32()函数创建的物理内存前8M的恒等转化在这种映射不再必要的时候,需要调用zap_low_mappings()进行撤销。


2.3当RAM在896M和4096M之间的最终内核页表


这种情况下,并不把RAM全部映射到内核地址空间。linux在初始化阶段将把一个具有896MB的窗口映射到内核线性地址空间。如果一个程序需要对896M以上的地址进行寻址,那么就必须把线性地址映射到对应的RAM,这意味这修改某些页表项的值。内核使用与前一种情况相同的代码来初始化页全局目录。


2.4当RAM大于4096MB时候的最终内核页表


此时,线性地址只有1G和RAM大于1G,此处的映射就可能涉及到PAE和高端内存,详细可以参考高端内存。此时,linux仅仅映射前896M的RAM,剩下的不进行映射(剩下的就用于存放用户数据了)。与前两种的主要差异在于,此时,采用三级分页模型,代码如下:


。。。。。。

页全局目录的前三项与用户线性地址空间对应,内核使用一个空页(empty_zero_page)进行初始化,第四项,采用页中间目录的地址进行初始化,改页中间目录是通过调用alloc_bootmem_low_pages()获得的。页中间目录的前448项用RAM的物理地址填充。

然后页全局目录的第四项被拷贝到第一项中,这样好为线性地址空间的前896M中的第物理内存映射做镜像。为了完成对SMP系统的初始化,这个映射是必须的,初始化完成以后,内核调用zap-low_mappings()清楚对应的页表项。

kmap的实现分析实验 kmap的实现分析 kmap的实验 阅读详情

相关推荐

内核页表初始化

内核内存初始化 暂时用 2.6.11 x86 平台的内核来做讲解。 从 bois 中获取各种配置信息,我也不懂,所以仅介绍结果 该篇文章已假设你了解了内核的内存总体的分布,如果没有请参考我的这篇文章 物理内存分布 首先可用的物理内存(地址)不是连续分布的,有很多空洞。因为这些空洞要么不可用,要么被其他外设占用,所以在内核初始化时,必须从bois中获取可用于物理内存。另外由于内存只识别 4K...

CrazyHeroZK的专栏 826

内存寻址——分页

Linux中的分页 分页单元把线性地址转换为物理地址,为了效率起见,线性地址被分成以固定长度为单位的组,成为页。这样页内部连续的线性地址被映射到连续的物理地址中。这样,内核就可以指定一个页的物理地址和其存取权限,而不用指定页所包含的全部线性地址的存取权限。 分页单元把所有的RAM分成固定长度的页框(有时也叫做物理页)。每个页框包含一个页,也就是说一个页框的长度一个页的长度一致。页框是主存的一部分...

Jason_Linux 1068

LINUX进程管理之进程管理初始化

三、关键函数 1:sched_init () 逐行全解(kernel/sched/core.c)核心对象:0 号 init_task、init_mm、调度器、1 号 / 2 号进程。swapper_pg_dir 是 init_mm 的底层页表,后续所有内核进程共享;汇编 head.S → 建 swapper_pg_dir → 绑定 init_mm。五、关键函数 2:rest_init () 逐行全解(init/main.c)rest_init → 创 1 号 kernel_init (用户进程父)

qitian_8615的博客 242

arm linux 进程页表,ARM linux 建立页表过程

paging_init 用来建立页表初始化zone的memory mapvoid *zero_page;sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);build_mem_type_table();sanity_check_meminfo();prepare_page_ta...

weixin_28673669的博客 345

linux内存管理:kmap、vmap、ioremap

目录 散列表也是哈希表 kmap实现 page_address_map pkmap_count page_address_slot 哈希函数 kmap函数实现 kmap_init kmap kmap_high page_address map_new_virtual set_page_address kunmap kunmap_high(struct page *page)函数实现 vmap实现 vmap函数 vunmap函数 ioremap ioremap函数 _

RToax 8876

MIT6.S081 Lab3: Page Tables 实战解析——从独立内核页表到硬件加速的数据拷贝

本文深入解析MIT6.S081 Lab3中xv6操作系统的页表机制,从独立内核页表实现到硬件加速数据拷贝的优化策略。通过实战案例展示如何修改进程控制块、初始化内核页表,并利用MMU硬件加速提升系统调用性能3-5倍。特别探讨了页表切换时机、内存同步机制等关键问题,为理解现代操作系统内存管理提供实践参考。

weixin_33686714的博客 316

【Linux 驱动开发】Linux 内核启动过程详解

本文系统梳理了ARM架构下Linux内核的启动流程,分为四个关键阶段:引导加载(U-Boot加载内核镜像和设备树)、自解压(head.S调用解压函数)、汇编级准备(stext函数完成硬件初始化)和C语言初始化(start_kernel完成系统初始化)。重点解析了各阶段核心任务:U-Boot参数传递、内核解压、处理器验证、页表建立、命令行参数解析,直至最终启动init进程。文中还提供了mkimage工具的使用说明和关键函数调用关系图,为理解Linux内核启动机制提供了清晰的框架。通过结合源码日志。

猫猫的小茶馆 520

嵌入式linux内核启动过程详解,图文详解!嵌入式Linux内核启动主要分为这三个阶段...

嵌入式linux内核的启动全过程主要分为三个阶段。***阶段为内核自解压过程,第二阶段主要工作是设置ARM处理器工作模式、使能MMU、设置一级页表等,而第三阶段则主要为C代码,包括内核初始化的全部工作,下面是详细介绍。一、Linux内核自解压过程在linux内核启动过程中一般能看到图1内核自解压界面,这里重点讨论内核的自解压过程。内核压缩和解压缩代码都在目录kernel/arch/arm/boot...

weixin_39984952的博客 1479

内核启动流程源码级分析

本文深入剖析了Linux 2.6.20内核启动流程,从BIOS/UEFI到init进程的完整启动链路。重点分析了汇编入口startup_32的关键步骤:设置段寄存器、清空BSS段、初始化页表并启用分页机制,以及从汇编跳转到C语言入口start_kernel的过程。start_kernel函数作为内核初始化的总指挥,依次完成处理器ID设置、中断禁用、调试机制初始化等核心工作,为后续各子系统的初始化奠定基础。整个启动过程展现了内核如何在资源受限环境下完成从底层硬件到完整操作系统的精密构建。

海绵宝宝de派小星的博客 951

xv6,lab3-A kernel page table per process(写这个博客的目的是保留自己的学习记录,以便回顾)

修改。实验开始第一步。进程得有个地方存放它的页表地址。

weixin_52264865的博客 404

duckOS如何启动?Multiboot引导C++内核初始化全流程深度剖析

**duckOS** 是一个用现代 C++ 编写的 x86 单体内核操作系统。本文带你完整拆解 duckOS 的**启动过程**:从 Multiboot 引导头被 GRUB/QEMU 识别,到汇编入口 `start` 建立分页,再到 C++ 内核函数 `kmain` 初始化内存、中断、设备,最终执行 `/bin/init` 进入图形桌面。无需读源码也能看懂这条"开机链路"。 ## duckOS

gitblog_00439的博客 356

计算机启动流程完全指南:从BIOS到第一个进程的终极解析

你是否曾好奇当你按下电脑电源键时,计算机内部究竟发生了什么?从BIOS自检到操作系统加载,再到第一个进程的启动,这一系列复杂而精密的流程构成了现代计算机的启动过程。本文将带你深入探索计算机启动的完整流程,揭开从硬件初始化到软件执行的神秘面纱。 ## BIOS/UEFI:启动过程的序幕 计算机启动的第一步始于BIOS(基本输入输出系统)或UEFI(统一可扩展固件接口)。当你按下电源键时,CPU会

gitblog_00396的博客 367

Linux初始化(下):从_start到第一个进程

你好,我是程序员贵哥。今天我们继续来研究Linux的初始化流程,为你讲解如何解压内核,然后讲解Linux内核第一个C函数。最后,我们会用Linux的第一个用户进程的建立来收尾。如果用你上手去玩一款新游戏做类比的话,那么上节课只是新手教程,而这节课就是更深入的实战了。后面你会看到很多熟悉的“面孔”,像是我们前面讲过的CPU工作模式、MMU页表等等基础知识,这节课都会得到运用。

qq_33240556的博客 713

《Linux内核源码情景分析》第十章 系统引导和初始化 内容详细梳理

随后,GRUB会在屏幕上显示启动菜单,允许用户选择要启动的内核版本(若配置了多个内核),若用户未操作,会在超时后自动启动默认内核。系统初始化第二阶段是整个初始化过程的核心,对应源码中的init/main.c文件中的start_kernel函数,该函数是Linux内核C语言代码的入口,几乎初始化内核所有的核心子系统(内存管理、进程调度、中断管理、虚拟文件系统等),同时完成初始内存磁盘(initrd/initramfs)的挂载和驱动程序的初步加载,整个过程仍运行在内核态,且逐步建立起内核的完整工作机制。

weixin_44932366的博客 340

Linux 0.11 源码解读:从 5 个关键函数看进程创建切换的实现

本文深入解析Linux 0.11源码中进程创建切换的五个关键函数:`fork()`、`copy_process`、`schedule`、`switch_to`和`sys_pause`。通过代码级分析,揭示了操作系统进程管理的核心机制,包括进程复制、调度算法和上下文切换等关键技术,为理解操作系统原理提供了实践视角。

weixin_28396629的博客 253

mm_init()

摘要:Linux内核初始化中的mm_init()函数是内存管理子系统的核心初始化入口,在start_kernel()阶段执行。该函数负责构建物理和虚拟内存管理框架,使内核能够安全高效地分配/释放内存。mm_init()分为四个阶段:1)初始化内存管理核心框架和页表;2)建立伙伴系统管理物理内存;3)初始化Slab/Slub等小内存分配器;4)完善辅助机制和统计功能。该过程为后续模块提供内存分配能力,90%代码架构无关,仅页表等细节需适配不同硬件平台。最终使alloc_pages()、kmalloc()等内存

tangzhangyin的专栏 526

DyscheOS-kernel核心组件详解:从进程管理到内存分配机制

前往项目官网免费下载:[https://ar.openeuler.org/ar/](https://ar.openeuler.org/ar/?utm_source=csdn_blog) DyscheOS-kernel作为开源操作系统内核,提供了关键的系统核心功能,包括进程管理、内存分配等核心组件。本文将深入解析这些核心机制的工作原理,帮助开发者和爱好者理解操作系统内核的基础架构。 ## 一、进

gitblog_06805的博客 169

从MMU到IOMMU:聊聊Linux内核里那些“翻译官”是怎么保护你的内存安全的

本文深入探讨了Linux内核中MMU和IOMMU如何协同工作以保护内存安全。从地址翻译机制到IOMMU的初始化流程,详细解析了这些'翻译官'如何防止非法内存访问,并提供了在Linux系统中配置IOMMU的实用指南,帮助开发者提升系统安全性。

weixin_30345577的博客 315
上一篇: ORACLE 10046事件详解-转载
下一篇: Android实时监控项目第四篇:后台线程发送预览帧视频数据
iteye_7527
博客等级 码龄8年 63粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值