Linux内存初始化(汇编部分)
之前有几篇博客详细介绍了Xen的内存初始化,确实感觉这部分内容蛮复杂的。这两天在看Linux内核启动中内存的初始化,也是看的云里雾里的,想尝试下边看边写,在写博客的过程中慢慢思考,最后也能把自己的思考分享给其它人。
这个系列主要分为两个部分,汇编部分和C语言部分。
这篇博文主要介绍的是汇编部分。
注:这两篇博文介绍的都是64位系统。
内核解压缩过程
这个过程就不详述了,整个Linux内核是作为一个压缩过的镜像提供的,在执行内核代码之前,首先需要bootloader对其进行一个解压缩,对这部分有兴趣可以参看这篇博客。
最初的页表什么样?
解压结束后,会进行一个对elf格式的parse,然后对内核进行加载,最后进入arch/x86/kernel/head_64.S
中的startup_64
。
startup_64
主要完成分页功能启用,最后跳入C代码x86_64_start_kernel。在开始分析代码之前,我们要先来看看在内核的数据段中,初始化页表是长怎么样的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
这段数据结构还是比较清楚的,你把下面这两个宏NEXT_PAGE
和PMDS
代入上面的数据结构:
1 2 3 4 5 6 7 8 9 10 11 |
|
我们就可以很轻易地画出下面这张图:
后面的初始化过程,就是建立在这个早期的页表结构中的。
正式进入startup_64
我们一段段来分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
这里的这段代码非常奇怪:
1 2 |
|
我想了好久,现在终于在Liangpig的指导下有了点眉目。(不确定的)解释如下:
首先leaq _text(%rip), %rbp
是一个相对寻址的指令,其并不是直接将_text
的地址和当前%rip
的值相加,而是%rip
加上一个_text
和它的相对地址,其实就是$-7
(因为该地址的长度为7,而当前的%rip
就是_text
地址加上7
),这个相对值是在link的时候计算出来的,可以参看这个问题和这个问题。
这里另外需要注意的一点是,在当前这个时候,计算机还是通过实模式进行寻址的,所以内核的代码应该是被load到了一个低地址(而不是大于0xffffffff8000000
的地址),因此,%rbp
存储的也是一个低地址,表示的是内核的代码段被实际装载到内存到的地址,让我们假设是0x3000000
。
那么$_text - __START_KERNEL_map
是什么呢?我们来看下面的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
首先,__START_KERNEL_map
是0xffffffff80000000
,即内核代码和数据段在64位的虚拟地址空间中的最低地址段(0xffffffff80000000
到0xffffffffa0000000
这512MB的虚拟机之空间映射了内核段)。而_text
表示的是__START_KERNEL_map
加上了一段编译过程中指定的地址,在我机器内核的.config
文件中为0x1000000
。也就是说,如果__START_KERNEL_map
映射的是物理地址为0
的内存的话,那么在编译中我们期望的真正的物理地址就为0x1000000
,也就是说,$_text - __START_KERNEL_map
表示的是我们在编译过程中期望的内核段被装载到内存的起始地址,因此subq $_text - __START_KERNEL_map, %rbp
表示将当前内核段真实被装载到内存中的地址和编译过程中期望被装载到内存中的地址的差值赋值给%rbx
,在我们的例子中即为0x2000000
(0x3000000
- 0x1000000
)。
之后我们就对这个真实被装载到内存中的地址做一些检查,包括是否2M对齐,以及有没有超过最大大小等等,这里就不详述了。
然后做的一件事就是调整初始化页表中的物理地址映射:
1 2 3 4 5 6 7 8 9 |
|
这又是一段相对寻址,由于页表处于数据段,所以需要根据其和%rip
中的相对地址来定位到页表,然后将页表中的表项加上之前计算的相对偏移量。当然这里只处理了early_level4_pgt
、level3_kernel_pgt
和level2_fixmap_pgt
,而真正映射内核段的level2_kernel_pgt
会在之后进行fixup。
之后又进入了一段诡异的代码,来建立identity mapping for the switchover
,我也不懂这里的switchover
是什么,我们先来看下这段代码做了什么吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
我们可以稍微进行一个计算,首先%rdi
保存了当前内核代码段的首地址,%rbx
保存了early_level4_pgt
的地址,%rax
是内核代码首地址对于level4页表的index,在当前即为0。所以leaq (4096 + _KERNPG_TABLE)(%rbx), %rdx
表示的是将early_level4_pgt
所在的地址加上一个页的地址,作为第3级页表页,再加上相应的权限位,保存在%rdx
中,然后通过movq %rdx, 0(%rbx,%rax,8)
和movq %rdx, 8(%rbx,%rax,8)
指令把%rdx
作为一个表项,存在early_level4_pgt
的第0和第1项中。
然后将%rdx
再加上一个页的大小,作为第2级页表页,找到内核代码段对于level3页表的index,然后将第2级页表页加上对应的权限作为一个页表项存在刚刚建立的level3页表的第0项和第1项。
然后将%rbx
加上两个页的大小,即第2级页表的位置,找到从_text
到_end
所有内核代码段对于level2页表的索引,然后将对应的地址+权限作为页表项逐个填到这个第2级页表中。
我们可以在arch/x86/kernel/head_64.S
文件中找到这几个新添加的页表页的定义:
1 2 3 4 5 6 7 |
|
即紧接着early_level4_pgt
,被称为early_dynamic_pgts
。这个就是所谓的identity mapping for the switchover
,表示在之后的一小段页表转换过程中会被用到的identity mapping。因为在页表中虚拟地址从低地址到高地址转换的过程中不可避免的会通过低位的虚拟地址进行索引,所以需要预先做个identity mapping的准备。
至此,页表变成了这个样子。
startup_64
最后一步就是fixup内核段真正的物理页对应的页表项了,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
这个过程的前半部分就是将level2_kernel_pgt
中的表项进行一个个的检查,如果不是0(即为一个可能存在的页表项),则将其加上之前计算的真实地址和被期待地址的偏移量(%rbp
)。
当这个fixup结束之后,将%rbp
保存在phys_base
这个地址中,然后再将early_level4_pgt - __START_KERNEL_map
保存在%rax
中。
接下来就进入secondary_startup_64
。
secondary_startup_64
这部分代码的主要功能是一些模式的开启,以及相关数据结构的加载,我们同样逐段进行分析:
1 2 3 4 5 6 7 8 |
|
这里开启了PAE和PGE模式,并将其写到%cr4
中,同时将初始页表的第四级页表地址写入了%cr3
。至此,分页模式开启!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
上面的代码进行了一系列的初始化,包括检查nx
(non-execution)是否开启,创建EFER,创建cr0,以及设置一个启动时会用到的栈,并且将所有eflags清零。这里就不细讲了。
然后是加载早期的GDT:
1 2 3 4 5 6 7 |
|
初始化段寄存器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
这里需要注意的是%gs
的建立,它和per cpu变量相关,是一个比较关键的段寄存器。不过由于这个系列主要是和内存相关,所以这里就不详述了。
然后将参数传给%rdi
:
1 2 3 |
|
最后就是一个通过far return的跳转:
1 2 3 4 5 6 7 8 9 10 11 |
|
其中initial_code
定义为:
1 2 |
|
这里要注意的是,在pushq $__KERNEL_CS
这条指令之前的寻址还是通过identity-map的页表进行寻址的,而在该指令之后,cs被赋值成__KERNEL_CS
,就变成采用正常的页表进行寻址了,到这个时候,虚拟地址就变成高位的地址了(0xffffffff80000000
~0xffffffffa0000000
)。
最后我们提一下这个lretq
,所谓的long return。可以参照这里:
For an intersegment (far) return, the address on the stack is a long pointer. The offset is popped first, followed by the selector.
所以在rax之前,需要把__KERNEL_CS
的selector也放在栈上。
最后一个问题:gdt是在什么时候初始化的?这个我一直都没有找到,这里就先不管了。
因此,最后进入了x86_64_start_kernel
函数,这是一个C语言写的函数,所以,会在下一篇博客中进行介绍。
原文地址:http://ytliu.info/blog/2016/03/14/linuxnei-cun-chu-shi-hua-assembly/
Linux内存初始化(汇编部分)相关推荐
- Linux内存初始化(一)
一.前言 一直以来,我都非常着迷于两种电影拍摄手法:一种是慢镜头,将每一个细节全方位的展现给观众.另外一种就是快镜头,多半是反应一个时代的变迁,从非常长的时间段中,截取几个典型的snapshot,合成 ...
- Linux内存初始化(C语言部分)
这篇博客接着上篇博客,继续介绍Linux内核启动过程中内存的初始化过程. 相比于汇编代码,分析C代码有一个优势,因为在之前的汇编代码中已经开启了分页模式,所以可以通过一些symbol直接在某些函数上设 ...
- 【嵌入式开发】ARM 内存操作 ( DRAM SRAM 类型 简介 | Logical Bank | 内存地址空间介绍 | 内存芯片连接方式 | 内存初始化 | 汇编代码示例 )
文章目录 一. 内存 简介 1. 两大内存分类 ( 1 ) DRAM 简介 ( 定期刷新 | 速度慢 | 成本低 ) ( 2 ) SRAM 简介 ( 不需刷新 | 存取速度快 | 功耗大 | 成本高 ...
- Linux内存初始化(四) 创建系统内存地址映射
一.前言 经过内存初始化代码分析(一)和内存初始化代码分析(二)的过渡,我们终于来到了内存初始化的核心部分:paging_init.当然本文不能全部解析完该函数(那需要的篇幅太长了),我们只关注创建系 ...
- linux内存初始化初期内存分配器——memblock
2019独角兽企业重金招聘Python工程师标准>>> 1.1.1 memblock 系统初始化的时候buddy系统,slab分配器等并没有被初始化好,当需要执行一些内存管理.内存分 ...
- Linux初始化内存盘黑屏,详解linux内存磁盘初始化技术.doc
详解linux内存磁盘初始化技术 详解linux内存磁盘初始化技术 /5502266.html 关键词: HYPERLINK "/tag/initrd" \n _blankinit ...
- 万字整理,图解Linux内存管理所有知识点
Linux的内存管理可谓是学好Linux的必经之路,也是Linux的关键知识点,有人说打通了内存管理的知识,也就打通了Linux的任督二脉,这一点不夸张.有人问网上有很多Linux内存管理的内容,为什 ...
- Linux内存管理:知识点总结(ARM64)
https://mp.weixin.qq.com/s/7zFrBuJUK9JMQP4TmymGjA 目录 Linux内存管理之CPU访问内存的过程 虚拟地址转换为物理地址的本质 Linux内存初始化 ...
- 万字整理,肝翻Linux内存管理所有知识点
Linux的内存管理可谓是学好Linux的必经之路,也是Linux的关键知识点,有人说打通了内存管理的知识,也就打通了Linux的任督二脉,这一点不夸张.有人问网上有很多Linux内存管理的内容,为什 ...
最新文章
- 2018年台湾为陕西最大贸易伙伴
- JAX-RS 从傻逼到牛叉 5:资源的动态定位
- 教你用 Netty 实现一个简单的 RPC!
- vue中render: h = h(App)的详细解释
- 快速排序+时间测试(yyds)
- php 解析java map,java_java遍历Map的几种方法分析,本文实例分析了java遍历Map的几 - phpStudy...
- 视频编辑,4k播放,3D游戏, 阿里云图形工作站,了解一下?
- boost::asio::deadline_timer(理解)
- 麦亡9什么时候能装鸿蒙系统,距断供不到10天 麒麟9000即将绝版 华为大招来了:不止鸿蒙2.0...
- 使用Visual Studio SDK制作GLSL词法着色插件
- ITK简介与ITK Pipeline
- CSS侦测方法(侦测是否支持某个CSS属性)
- UnsatisfiedDependencyException
- CVPR2020——D3VO论文阅读
- MySQL查看锁及事务隔离级别的命令
- 解码快手新市井电商,新品牌流量多,大品牌政策好
- 量子计算机核心技术突破,量子芯片研发有突破 我们距离量子计算机不远了
- 怎样让表格的行高一样_怎么把excel的行高变成一样的
- git pull 下拉代码报错 There is no tracking information for the current branch. Please specify which branc
- 如何基于用户生命周期分析,寻找新的增长点