ARM架构内核启动分析-head.S(1.3、stext分析之内存临时页表建立)

2024-05-09 22:38

本文主要是介绍ARM架构内核启动分析-head.S(1.3、stext分析之内存临时页表建立),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.2.4、创建临时页表:

对于创建临时页表,使用的是arm的L1主页表,L1主页表也称为段页表(section page table,说白了就是采用段式管理而不是页式管理),它将4GB的地址空间分成若干个1MB的段(section),因此L1页表包含4096个页表项(section entry);每个页表项是32 bits(4 bytes), 所以L1页表占用 4096*4 = 16k的内存空间;

L1页表,实际是给4G的虚拟地址空间中每个1M空间的段,映射其对应的物理内存地址是哪里,通过在对应的页表项写入物理地址值及MMU相关内容实现,在临时页表中,并没有写满所有表项,因为这时需考虑的只有内核代码。

另外注意在此时,R8存储machine_desc的物理地址;R9存储CPU ID,R10存储procinfo。

         /*创建临时页表*/

         bl      __create_page_tables

         __create_page_tables:

                  pgtbl         r4                                 @ page table address   

通过宏pgtblr4设置成页表的物理地址,KERNEL_RAM_PADDR - 0x4000 = 0x4000,即内核的前面是页表,head.S文件前面的代码有pgtbl宏声明的地方如下:

.macro     pgtbl, rd                                  

定义了一个宏pgtbl等于寄存器rd的值

ldr    \rd, =(KERNEL_RAM_PADDR - 0x4000)              

(KERNEL_RAM_PADDR - 0x4000)这个地址值赋给rd寄存器,一般这个地址用来存放临时页表,这也就让宏pgtbl的值为(KERNEL_RAM_PADDR - 0x4000 = 0x4000)

         /*

          * Clear the 16K level 1 swapper page table

          */

                   /*1、下面是把页表清零,页表是在内核之前16KB位置*/

                  mov r0, r4         

R0也保存页表起始物理地址

                  mov r3, #0         

R3为0

                  add  r6, r0, #0x4000

R6保存页表结尾地址

1:      str    r3, [r0], #4   

R3的值(0)存储在R0指向地址处,并且R0加4即指向的地址加4

                  str    r3, [r0], #4   

不断这样操作,意即不断清零R0到R6地址处即页表,直到R0指向地址和R6相同

                  str    r3, [r0], #4

                  str    r3, [r0], #4

                  teq   r0, r6

                  bne  1b

                   /*2、把R10保存的地址再加PROCINFO_MM_MMUFLAGS后,取该地址的值赋给R7,R10先前已保存了procinfo的地址,再加8就是proc_info_list结构变量的__cpu_mm_mmu_flags成员地址,取该成员的值赋给R7,这个成员的具体意义与arm MMU相关,需要仔细查看arm手册,这是下一阶段学习的重点!*/

                   ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

         /*

          * Create identity mapping for first MB of kernel to

          * cater for the MMU enable.  This identity mapping

          * will be removed by paging_init().  We use our current program

          * counter to determine corresponding section base address.

          */

                   /*3、下面这堆内容,其实是把L1页表中当前运行地址所在的段(当前以汇编代码运行的内核代码段,运行在物理内存起初的部分,取值应在0x0到0x00100000范围内,可以看看KERNEL_RAM_PADDR值为0x00008000,可见从L1页表的分段角度,在第一个段即地址0x0-0x00100000段范围内)的表项写入映射关系,事实上重点是理解为什么要把这里也做映射:

           之所以要为这个物理地址的段也做映射,是因为现在MMU没开启,过一会就会开启MMU,按说内核代码段的内容在做了映射后,CPU给MMU的PC值是虚拟地址,但由于CPU流水线的预取指功能,还是会有一些PC值还不是虚拟地址而仍然是物理地址,这样的话如果MMU内部没有相应的映射关系,将不知道访问实际物理内存哪部分,导致出现问题,所以这里需要把这部分物理地址在L1页表的相应表项也写入,本质是PA = PA(很多参考文章写PA = VA,其实道理是一样的,但那样表述容易给人以误解,因为开启MMU后,CPU发出的访问按说都是虚拟地址,但确实有一些其实还是物理地址,如果不做映射将让MMU无从知道该访问哪里,所以这里写L1页表的相关物理页表项,其实是做PA = PA的映射给MMU)*/

         mov r6, pc                                              

R6获取到PC值

         /*然后获取到kernel的section的物理地址保存在R6,R6 = 0x00008xxx(PC当前值)*/

         mov r6, r6, lsr #20                     @ start of kernel section  

通过pc值的高12位(右移20位),得到kernel的section,并存储到r6中,                                                     因为当前是通过运行时地址得到的kernel的section,因而是物理地址,                                                        所以R6 = 0x00008xxx << 20 = 0

    /*然后获取到写页表应该写什么值,并保存在R3,R3 = R7 + 0 = R7 = PROCINFO_MM_MMUFLAGS,R6刚才已保存了当前正在执行的内核代码(PC)所在的段值,这里左移20位得到该段地址起始值(这里即0x00000000,含义即物理地址的kernel base值),      再加上R7即PROCINFO_MM_MMUFLAGS,这个就是要写入L1页表表项的内容(具体为什么填入这个和MMU有关,还需后续仔细研究!)*/

         orr    r3, r7, r6, lsl #20                @ flags + kernel base      

flags + kernel base,得到页表中需要设置的值保存在R3

         /*接下来实际写页表,每个L1页表的表项长度为4字节,为了定位这个页表项在L1页表的位置或者说算出其在L1页表的偏移,所以左移2位即乘以4得出偏移值,再加上基地址R4,得出写在哪个L1页表的表项*/

         str    r3, [r4, r6, lsl #2]               @ identity mapping     

R4已保存了页表物理起始地址,R6保存了页表物理结束地址,上一步R3也保存了需要设置页表的值,这里把R3的值写入从R4+R6*4 = R4字节空间处,即写页表的第一个条目的值为R3值。

         /*

          * Now setup the pagetables for our kernel direct

          * mapped region.

          */

         /*4、下面这堆内容,核心目的是为kernel镜像(即内核代码段)做L1页表映射,即KERNL_START到KERNEL_END建立内存映射,这两个宏是编译时产生的,所以它们是虚拟地址,而内核代码段在物理内存的实际位置是从物理内存起初的部分(L1页表角度看就是在第一个段内),后续要开启MMU了,即CPU发给MMU的地址将是虚拟地址了,所以这里要先把内核代码做映射,映射方式方法和前面是一样的,把虚拟的0xc0008000到KERNEL_END(一般内核代码长度为3-4M,所以KERNEL_END值估计为0xc03XXXXX),映射到物理地址的0x0到0x3段内(假定内核代码为3-4M长度大小)*/

         add  r0, r4,  #(KERNEL_START & 0xff000000) >> 18    

R4已保存了页表物理起始地址,对于(KERNEL_START & 0xff000000),这是为了获取高12bit位的段值(为什么不直接以0xfff00000去与?),

所谓右移18位,其实就是先右移20位获取到段值,然后左移2位取得所在表项在L1页表的偏移值(因为每个表项占4个字节),

最后加上R4即L1页表物理基地址,得出该表项实际的物理地址保存在R0(实际值为0x20004000+0xC00*4 = 0x20007000)

         str    r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!  

R3即内核物理起始地址段值和PROCINFO_MM_MMUFLAGS的或运算结果即映射结果写入内核起始虚拟地址所在的L1表项,

对于我们这里就是写入的是0x0与PROCINFO_MM_MMUFLAGS的或运算结果,这里的疑问是:为什么不一开始就用0xfff00000去与运算,而是分两次?

         ldr    r6, =(KERNEL_END - 1)                          

r6为内核代码的尾部虚拟地址

         add  r0, r0, #4                                     

R0保存下一个即将要填写的L1页表项的物理地址

         add  r6, r4, r6, lsr #18                             

R6本身是内核代码的尾部虚拟地址,右移18位(右移20再左移2,先获取段值然后算出所在L1页表项的偏移),再加上R4值,最后R6值是内核代码结尾的所在L1页表的表项的物理地址

1:      cmp r0, r6

         add  r3, r3, #1 << 20                               

R0是内核代码起始处所在L1页表项物理地址,R6是内核代码结尾处所在L1页表项物理地址,R3是内核代码起始处对应的物理地址映射值;

         strls r3, [r0], #4                               

之后的内核代码对应的物理地址映射值将是其段值相应加1,即0xc00-0x000、0xc01-0x001、0xc02-0x002、0xc03-0x003.....

         bls    1b                                             

对于L1页表项,其实是写第0xc00、0xc01、0xc02、0xc03个条目的表项内容(物理地址在0x20007000、0x20007004、0x20007008、0x2000700c)

接下来的一段由宏CONFIG_XIP_KERNEL定制的代码无需关注;

/ *

* Then map first 1MB of ram in case it contains our boot params.

*/

/*5、通常kernel的启动参数由bootloader放到了物理内存的第1个M上,所以需要为RAM上的第1个M建立映射下面这堆内容,核心目的就是确保RAM的第1个M建立映射;

 事实上上面已为PHYS_OFFSET + TEXT_OFFSET建立了映射(如果TEXT_OFFSET小于0x00100000即1MB的话),我们这里TEXT_OFFSET值为0x8000,即也为SDRAM的第一个M建立了映射;

         说白了,这里的用意就是说无论上面代码如何,均为SDRAM的第一个M建立映射,事实上是为了考虑TEXT_OFFSET值大于0x00100000即1MB的情况,即内核起始代码超出了第一个段的范围的情况*/

         add  r0, r4, #PAGE_OFFSET >> 18

         orr    r6, r7, #(PHYS_OFFSET & 0xff000000)

         .if      (PHYS_OFFSET & 0x00f00000)

         orr    r6, r6, #(PHYS_OFFSET & 0x00f00000)

         .endif

         str    r6, [r0]

接下来的一堆宏定制的代码无需关注,最后是返回stext,这时临时页表建立成功,但仅仅是建立成功还未真正使用,真正使用还在后面。

/*6、返回函数stext调用__create_page_tables的地方*/

         mov pc, lr

临时页表创建后:

R4: 页表起始处物理地址

      R8: machine info,struct machine_desc的基地址

      R9: cpu id

      R10: procinfo,struct proc_info_list的基地址

并且映射了如下部分的页表映射: 物理内存第一MB(自己映射自己,因为开启MMU后仍有一些访问是以物理地址发到MMU)、内核代码段(基本是0xc00段到0xc03段,映射至0x0段到0x003段), 后续准备开启MMU!*/

这篇关于ARM架构内核启动分析-head.S(1.3、stext分析之内存临时页表建立)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/974708

相关文章

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

mysql中的服务器架构详解

《mysql中的服务器架构详解》:本文主要介绍mysql中的服务器架构,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、mysql服务器架构解释3、总结1、背景简单理解一下mysqphpl的服务器架构。2、mysjsql服务器架构解释mysql的架

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

k8s上运行的mysql、mariadb数据库的备份记录(支持x86和arm两种架构)

《k8s上运行的mysql、mariadb数据库的备份记录(支持x86和arm两种架构)》本文记录在K8s上运行的MySQL/MariaDB备份方案,通过工具容器执行mysqldump,结合定时任务实... 目录前言一、获取需要备份的数据库的信息二、备份步骤1.准备工作(X86)1.准备工作(arm)2.手

关于MyISAM和InnoDB对比分析

《关于MyISAM和InnoDB对比分析》:本文主要介绍关于MyISAM和InnoDB对比分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录开篇:从交通规则看存储引擎选择理解存储引擎的基本概念技术原理对比1. 事务支持:ACID的守护者2. 锁机制:并发控制的艺

nginx启动命令和默认配置文件的使用

《nginx启动命令和默认配置文件的使用》:本文主要介绍nginx启动命令和默认配置文件的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录常见命令nginx.conf配置文件location匹配规则图片服务器总结常见命令# 默认配置文件启动./nginx