读Kernel感悟-Linux内核启动-开启页面映射

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

文章来源:http://www.top-e.org/jiaoshi/class/

 

在setup的帮助下,我们顺利地从16位实地址模式过渡到32位段式寻址的保护模式。又在arch/i386/boot/compressed/head.S的帮助下实现了内核的自解压,并且从arch/i386/kernel/head.S中的startup_32开始。现在在线性地址0x100000(1M)处开始就是我们的解压后的内核了。而startup_32()的地址恰好是0x100000。由于还没有开启页面映射,所以必须引用变量的线性地址(即变量的虚拟地址-PAGE_OFFSET),带来了很多不便。所以下一步的任务,就是建立页表,开启页面映射了。我们不妨从arch/i386/kernel/head.S入手。

由于在Linux中,每个进程拥有一个页表,那么,第一个页表也应该有一个对应的进程。通常情况下,Linux下通过fork()系统调用,复制原有进程,来产生新进程。然而第一个进程该如何产生呢?既然不能复制,那就只能像女娲造人一样,以全局变量的方式捏造一个出来。它就是init_thread_union。传说中的0号进程,名叫swapper。只要swapper进程运行起来,调用start_kernel(),剩下的事就好办了。不过,现在离运行swapper进程还差得很远。关键的一步,我们还没有为该进程设置页表。

为了保持可移植性,Linux采用了三级页表。不过x86处理器只使用两级页表。所以,我们需要一个页目录和很多个页表(最多达1024个页表),页目录和页表的大小均为4k。swapper的页目录的创建与该进程的创建思维类似,也是捏造一个页表,叫swapper_pg_dir.

417 ENTRY(swapper_pg_dir)

418         .fill 1024,4,0

它的意思是从swapper_pg_dir开始,填充1024项,每项为4字节,值为0,正好是4K一个页面。

页目录有了,接下去看页表。一个问题产生了。该映射几个页表呢?尽管一个页目录最多能映射1024个页表,每个页表映射4M虚拟地址,所以总共可以映射4G虚拟地址空间。但是,通常应用程序用不了这么多。最简单的想法是,够用就行。先映射用到的代码和数据。还有一个问题:如何映射呢?运行cat /proc/$pid/maps可以看到,用户态进程的地址映射是断断续续的,相当复杂。这是由于不同进程的用户空间相互独立。但是,由于所有进程共享内核态代码和数据,所以映射关系可以大大简化。既然内核态虚拟地址从3G开始,而内核代码和数据事实上是从物理地址0x100000开始,那么本着KISS原则,一切从简,加上3G就作为对应的虚拟地址好了。由此可见,对内核态代码和数据来说:虚拟地址=物理地址+PAGE_OFFSET(3G)

内核中有变量pg0,表示对应的页表。建立页表的过程如下:

091 page_pde_offset = (__PAGE_OFFSET >> 20);

092 

093         movl $(pg0 - __PAGE_OFFSET), %edi

094         movl $(swapper_pg_dir - __PAGE_OFFSET), %edx

095         movl $0x007, %eax                       /* 0x007 = PRESENT+RW+USER */

096 10:

097         leal 0x007(%edi),%ecx                   /* Create PDE entry */

098         movl %ecx,(%edx)                        /* Store identity PDE entry */

099         movl %ecx,page_pde_offset(%edx)         /* Store kernel PDE entry */

100         addl $4,%edx

101         movl $1024, %ecx

102 11:

103         stosl

104         addl $0x1000,%eax

105         loop 11b

106         /* End condition: we must map up to and including INIT_MAP_BEYOND_END */

107         /* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */

108         leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp

109         cmpl %ebp,%eax

110         jb 10b

111         movl %edi,(init_pg_tables_end - __PAGE_OFFSET)

用伪代码表示就是:

typedef unsigned int PTE;

PTE *pg=pg0;

PTE pte=0x007;

for(i=0;;i++){//把线性地址i*4MB~(i+1)*4MB-1(用户空间地址)和3G+i*4MB~3G+(i+1)*4MB-1(内核空间地址)映射到物理地址i*4MB~(i+1)*4MB-1

swapper_pg_dir[i]=pg+0x007;

swapper_pg_dir[i+page_pde_offset]=pg+0x007;

for(j=0;j<1024;j++){

pte+=0x1000;

pg[i*1024+j]=pte;

}

if(pte>=((char*)pg+i*1024+j)*4+0x007+INIT_MAP_BEYOND_END)

{

init_pg_tables_end=pg+i*0x1000+j;

break;

}

}

大致意思是从0开始,把连续的线性地址映射到物理地址。这里的0x007是什么意思呢?由于每个页表项有32位,但其实只需保存物理地址的高20位就够了,所以剩下的低12位可以用来表示页的属性。0x007正好表示PRESENT+RW+USER(在内存中,可读写,用户页面,这样在用户态和内核态都可读写,从而实现平滑过渡)。

那么结束条件是什么呢?从代码中可知,当映射到当前所操作的页表项往下INIT_MAP_BEYOND_END(128K)处映射结束。nm vmlinux|grep pg0得c0595000。据此可以计算总共映射了多少页(小学计算题:P)

所以映射了2个页表,映射地址从0x0~0x2000-1,大小为8M。

最后,关键时刻到来了:

183 /*

184  * Enable paging

185  */

186         movl $swapper_pg_dir-__PAGE_OFFSET,%eax

187         movl %eax,%cr3          /* set the page table pointer.. */

188         movl %cr0,%eax

189         orl $0x80000000,%eax

190         movl %eax,%cr0          /* ..and set paging (PG) bit */

开启页面映射后,可以直接引用内核中的所有变量了。不过离start_kernel还有点距离。要启动swapper进程,得首先设置内核堆栈。

193         /* Set up the stack pointer */

194         lss stack_start,%esp

然后设置中断向量表,看到久违的"call"了

215         call setup_idt

检查CPU类型

载入gdt(原来的gdt是临时的)和ldt

302         lgdt cpu_gdt_descr

303         lidt idt_descr

最后,调用start_kernel

327         call start_kernel

到这一步,我们的目的地终于走到了。在摆脱了晦涩的汇编之后,接下去的代码,虽然与用户态程序相比,还有中断,同步等等的干扰,但相比较而言就好懂很多了。

Redis延迟毛刺元凶:Transparent Huge Pages(THP)与jemalloc冲突详解 Transparent Huge Pages(THP)是Linux内核为提升内存访问效率而设计的大页自动管理机制,其通过khugepaged后台线程合并4KB小页为2MB大页,虽可降低TLB缺失率,但会引发阻塞式内存迁移;jemalloc作为Redis默认内存分配器,依赖精细化arena管理和MADV_DONTNEED主动归还内存,与THP的页合并逻辑产生根本性冲突——当jemalloc尝试释放页时,恰逢khugepaged锁定页进行合并,导致Redis主线程卡顿数十毫秒。该问题在高并发、低延迟场景下表现为 阅读详情

相关推荐

带你玩转Linux内核物理页面page结构分析

1、思考问题?(答案:Linux操作系统原理) 当内存不足时,我们如何进行分配?当操作系统运行时候太长,产生很多很多内存碎片,此时我们应该怎么办?如果我们想要分配几十个字节的小块内存,应该使用什么样的方法来解决此问题?我们如何提高系统分配物理内存的效率? 一、物理页面page结构 32位的CPU寻址时按照数据位宽(字word),但是CPU在处理物理内存时即不是按照字进行来分配,因为现在的CPU都采用页分配机制直接来管理内存。所以在CPU里面有一个叫MMU的硬件单元。它会处理虚拟内存到物理内存

youzhangjing_的博客 1408

LLM服务架构蒸发:协调层归零与HTTP/2原生推理

大语言模型(LLM)服务架构正经历从‘多层协同’到‘单层内聚’的范式演进。传统依赖独立协调层(Orchestrator)实现会话管理、流式响应组装和Token计费的设计,因状态同步开销、序列化延迟与运维成本高企,逐渐成为性能瓶颈。Anthropic等前沿实践通过将HTTP/2 Server Push、Hash-Based无状态路由与CUDA内核级Token计量三大能力深度集成至vLLM执行层,使协调逻辑物理下沉、逻辑消失——即‘架构级蒸发’。该技术显著降低端到端延迟、消除级联故障域,并推动MLOps向GPU

weixin_30458043的博客 291

【清风数学建模笔记】第十一讲:时间序列分析

【清风数学建模笔记】第十一讲:时间序列分析

manerzi的博客 812

linux内核地址随机化-kaslr

开启内核地址随机化 Virtual kernel memory layout: modules:0xffffff8000000000 - 0xffffff8008000000 (128MB) vmalloc: 0xffffff8008000000 - 0xffffffbebfff0000 (250GB) .text: 0xffffff8008080000 - 0xffffff80...

yiduoxiaoxx的博客 5795

linux的swapper_pg_dir

linux启动涉及到一个解压与定位的过程,对于x86体系结构而言,系统被加载到0x100000的地方,那么swapper_pg_dir的值是什么呢?我们知道swapper_pg_dir是一个很重要的东西,它是所有进程内核空间的页表的模板,而且在涉及到896M以上的内存分配时,swapper_pg_dir也是一个同步的根,这些内存分配包括vmalloc区,高端永久区,高端临时区等。这里需要说明的是

大海蓝天的专栏 2996

漏洞利用缓解及对抗技术ALSR、KALSR、DEP、PXN

随着软件系统越来越复杂,软件漏洞变得无法避免。业界逐渐推出了让漏洞无法利用或利用难度提高的方法,简称漏洞缓解技术。我们简单介绍下Android和iOS中广泛使用的一些漏洞缓解及可能的绕过技术。当然这里也包含一些相关联的安全限制,而非真正意义的缓解技术。...............

键盘的起始页 1739

内核打开kaslr后的调试方法

本文基于ARM64平台代码分析,ARM64平台的内核,在编译链接时,kernel代码段被链接的位置是:KIMAGE_VADDR + TEXT_OFFSET。我们可以通过查看vmlinux.lds.S链接文件查看具体内容: . = KIMAGE_VADDR + TEXT_OFFSET; .head.text : { _text = .; HEAD_TEXT } 下面依次介绍各个变...

程序猿Ricky的日常干货 2612

ucore开启虚拟存储器过程详解

link脚本 在本项目中,有boot.ld和kernel.ld两个链接脚本 先来放几个常用链接脚本的注解: /DISCARD/ : {*(.comment)} //将输入文件的comment段丢弃 PROVIDE//该关键字定义一个(目标文件内被引用但没定义)符号。相当于定义一个全局变量,其他C文件可以引用它。 先来看第一个 OUTPUT_FORMAT("elf32-i386") OUTPUT_ARCH(i386) /** .data * .startu

weixin_44037337的博客 1439

Kernel感悟-Linux内核启动-链接脚本

文章来源:http://www.top-e.org/jiaoshi/class/一般来说,用户是不需要关心section的具体位置的。在用户态内核会解析elf可执行文件的各个section,然后把它映射到虚拟地址空间。然而,在内核启动时,一切得从零开始。很多在用户态下应用程序不需要操心的东西,例如映射section的任务不得不由内核自己来完成。上一篇感悟揭示了内核如何建立页表,并且把自身

李亚锋老师 1590

Linux kernel 分析之七:内核启动-链接脚本

一般来说,用户是不需要关心section的具体位置的。在用户态内核会解析elf可执行文件的各个section,然后把它映射到虚拟地址空间。然而,在内核启动时,一切得从  零开始。很多在用户态下应用程序不需要操心的东西,例如映射section的任务不得不由内核自己来完成。上一篇感悟揭示了内核如何建立页表,并且把自身的一部分映射到虚拟地址。内核还要负责对BSS段(所有在代码中未定义的全局变量)的初

vanileo的专栏 2552

Linux内核地址随机化(Kaslr)

Linux,内存管理,Kaslr

JiMoKuangXiangQu的专栏 1023

linux kernel struct 之 page

linux kernel struct 之 page

xiaozhi 1189

深入分析Linux内核源代码6-Linux 内存管理(1)

每天十五分钟,熟一个技术点,水滴石穿,一切只为渴望更优秀的你! ————零声学院 存储器是一种必须仔细管理的重要资源。在理想的情况下,每个程序员都喜欢无穷大、 快速并且内容不易变(即掉电后内容不会丢失)的存储器,同时又希望它是廉价的。但不幸 的是,当前技术没有能够提供这样的存储器,因此大部分的计算机都有一个存储器层次结构, 即少量、快速、昂贵、易变的高速缓存(cache);若干兆字节的中等速度、中等价格、易变 的主存储器(RAM);数百兆或数千兆的低速、廉价、不易变的磁盘。如图 6.1 所示,这些资 源.

资料qun832218493 3020

内核的最终页表

在上一节中,我们可以看到,在kernel/head.S中建立了临时页表

chengbeng1745的博客 541

ULK3书记录-CHAP2-内存管理

1. 逻辑地址 线性地址 物理地址 逻辑地址:和机器语言中出现的操作数,指令地址相关; 线性地址:0 - 2^32  4GB 物理地址:内存总线相关                             分段单元                   分页单元 MMU: 逻辑地址 -----------> 线性地址 -----------> 物理地址 2. 硬件中的分段 a)Inte

forsakening的专栏 1325

Linux内存描述之高端内存--Linux内存管理(五)

日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 http://blog.csdn.net/vanbreaker/article/details/75799411 前景回顾前面我们讲到服务器体系(SMP, NUMA, M

OSKernelLAB(gatieme) 1万+

linux启动过程(参考内核0.11)

当PC启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入到0x7C00,并开始执行此处的代码.这就

process的专栏 2090

深入理解Linux内核学习笔记之内存寻址(续)

Linux中的分页 Linux采用一种同时适用于32位和64位系统的普通分页模型。 Linux直到 2.6.10 版本, 采用三级分页模型; 从2.6.11开始,采用四级分页模型(用来全力支持*86_64平台使用的对线性地址的位的划分)。 4 种页表分别为:页全局目录(Page Global Directory),页上级目录(Page Upper Directory),

julie0107的专栏 1394

linux内存管理全貌,【Linux内存源码分析】开启分页管理

前面的初探内存保护模式里面,Linux最初进入保护模式,仅仅是一种纯段式的内存映射模式,而且也未起到很明显的保护作用,明显这不是linux内存管理的最终模式。Linux是不使用段保护的,使用的是页保护,所以它还需要开启分页管理。分页说简单也简单,就是通过页全局目录找到页表接着通过页表找到页面,诸如此类的查找映射方式。但是Intel支持有4k、2M、4M等不同的内存页面大小,不同的页面大小其映射方式...

weixin_30652105的博客 252
上一篇: 读Kernel感悟-Linux内核启动-内核解压缩
下一篇: 我的嵌入式Linux学习之路
TopEmbedded
博客等级 码龄18年 58粉丝 61原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值