软重启

// 定义一个函数指针
typedef void (*IpFunction)();
// 定义一个函数fun,其地址已经被指定
IpFunction fun = (IpFunction)0xf000fff0;
// 调用该函数,就是相当于把pc指针指到该处执行
fun();
  • 在c语言编程中,我们通常使用上述方法跳转到指定的内存地址处执行代码。

内存空间与IO空间

向客户机操作系统公开应公开硬件辅助的虚拟化_用户空间

  • 内存空间是必须的,IO空间不是必须的。

内存管理单元

内存管理单元就是(mmu)是一种硬件来着,人家辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、 内存访问权限保护和cache缓存控制等硬件支持。
mmu的基本概念:

  1. TLB(Translation Lookaside Buffer):转换旁路缓存,人家缓存少量的虚拟地址与物理地址的映射关系,称为快表。
  2. TTW(Translation Table Walk):转换表漫游,当在tlb没有找到对应关系时,就在ttw中查找对应关系,然后将这个映射关系再存入tlb中。
  • 上面是一个示意图,给定一个页表基地址,然后通过查询页表的方式最终查找到对应物理内存上的代码页或者数据页。

arm处理器访问内存过程

向客户机操作系统公开应公开硬件辅助的虚拟化_用户空间_02

  • 从上面可以看出处理器在访问存储器前先通过查询TLB,然后得到物理地址继续访问存储器;或者在通过高速缓冲的地址进行直接映射到物理地址,然后访问存储器。

arm处理器cpu进行数据访问的流程

向客户机操作系统公开应公开硬件辅助的虚拟化_虚拟地址_03

内核空间与用户空间

向客户机操作系统公开应公开硬件辅助的虚拟化_用户空间_04

  • 在linux32位系统中,一个进程的地址空间被划分为4G大小。其中分为两个部分,内核空间和用户空间。一般来说,用户空间大小为3G,内核空间大小为1G。当然这个比例是可以认为调节的。
  • 用户进程只能访问用户空间的虚拟地址,如果要访问内核空间的虚拟地址,需要通过系统调用,或其他内核中断来间接访问内核空间的虚拟地址。
  • 每个进程都有自己的页表,因此各个进程之间互不影响。

内核空间内容

  1. x86内存布局
  • 内核空间中又被划分为物理内存映射区、虚拟内存分配区、高端内存映射区、专用页面映射区和系统保留映射区。
  1. 32位arm系统中linux内核的内存布局
  • 上述假设的PAGE_OFFSET等于3GB
  • 将内核模块的地址空间放在靠近PAGE_OFFSET的16MB内是为了实现内核模块和内核代码段之间的短跳转。

内存存取

  1. 用户空间内存动态申请
  • 使用malloc申请内存;使用free释放内存
  • 在使用malloc的时候,内部会调用内核的系统调用函数,例如brk()和mmap()
  • c语言库中的malloc具有对内存的二次管理作用,优化使用
  • linux内核采取按需调页,只有在写到某个页面时,发生页错误后,内核才会从内存调页到这个进程中
  1. 内核空间内存动态申请
  • kmalloc()、__get_free_pages()和vmalloc()等。kmalloc()和__get_free_pages()(及其类似函数)申请的内存位于DMA和常规区域的映射区,,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。
  • vmalloc()在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,而vmalloc()申请的虚拟内存和物理内存之间也没有简单的换算关系。

slab和内存池

一方面,完全使用页为单元申请和释放内存容易导致浪费(如果要申请少量字节,也需要用1页);另一方面,在操作系统的运作过程中,经常会涉及大量对象的重复生成、使用和释放内存问题。在Linux系统中所用到的对象,比较典型的例子是inode、task_struct等。如果我们能够用合适的方法使得对象在前后两次被使用时分配在同一块内存或同一类内存空间且保留了基本的数据结构,就可以大大提高效率。slab算法就是针对上述特点设计的。实际上kmalloc()就是使用slab机制实现的。slab是建立在buddy算法之上的,它从buddy算法拿到2n页面后再次进行二次管理,这一点和用户空间的C库很像。slab申请的内存以及基于slab的kmalloc()申请的内存,与物理内存之间也是一个简单的线性偏移。

  • 使用cat /proc/slabinfo 可以查看当前slab的分配和使用情况
  • linux内核包含对内存池的支持,内存池技术是一种非常经典的用于分配大量小对象的后备缓存技术

IO端口和IO内存

  1. IO端口
  • 在Linux设备驱动中,应使用Linux内核提供的函数来访问定位于I/O空间的端口
  1. IO内存
  • 在内核中访问I/O内存(通常是芯片内部的各个I 2 C、SPI、USB等控制器的寄存器或者外部内存总线上的设备)之前,需首先使用ioremap()函数将设备所处的物理地址映射到虚拟地址上
  • 通过ioremap()获得的虚拟地址应该被iounmap()函数释放

设备IO端口和IO内存访问流程

  1. IO端口访问流程:
  2. 向客户机操作系统公开应公开硬件辅助的虚拟化_用户空间_05

  3. IO内存访问流程
  4. 向客户机操作系统公开应公开硬件辅助的虚拟化_内核空间_06

将设备地址映射到用户空间

  • mmap()这个函数可以使得用户空间直接访问设备的物理地址,当用户访问用户空间的这段地址时,实际上会转化为对设备的间接访问。
  • 这种对显示适配器一类的设备很有意义,如果用户空间可直接通过内存映射访问显存 的话,屏幕帧的各点像素将不再需要一个从用户空间到内核空间的复制的过程

当调用mmap函数的时候,对应的内核设备如果与mmap关联上的话,内核会进行如下处理:

  1. 在进程的虚拟空间中查找一块VMA
  2. 将这块 VMA进行映射
  3. 如果设备驱动程序中file_operations中的mmap有实际定义,则调用它
  4. 将这个 VMA插入进程的VMA链表中
  • 由mmap映射的虚拟地址解除映射时应该调用ummap函数
  • 驱动程序中mmap()的实现机制是建立页表,并填充VMA结构体中vm_operations_struct指针。
fault函数

在驱动程序中实现VMA的fault()函数通常可以为设备提供更加灵活的内存映射途径。当访问的页不在内存里,即发生缺页异常时,fault()会被内核自动调用,而fault()的具体行为可以自定义。这是因为当发生缺页异常时,系统会经过如下处理过程:

  1. 找到缺页的虚拟地址所在的VMA
  2. 如果需要的话,分配中间页目录表和页表
  3. 如果页表项对应的物理页不存在,则调用这个VMA的fault方法,它返回物理页的页描述符
  4. 将物理页的地址填充到页表中
  • 对于显示、视频等设备,建立映射可减少用户空间和内核空间之间的内存复制。
  • 对于向串口这种面向流的设备来说,实现这种映射没有意义

IO内存静态映射

  • 在将Linux移植到目标电路板的过程中,有得会建立外设I/O内存物理地址到虚拟地址的静态映射,这个映射通过在与电路板对应的map_desc结构体数组中添加新的成员来完成。

DMA

dma与cache一致性

假设DMA针对内存的目的地址与Cache缓存的对象没有重叠区域(如图11.12所示),DMA和Cache之间将相安无事。但是,如果DMA的目的地址与Cache所缓存的内存地址访问有重叠(如图11.13所示),经过DMA操作,与Cache缓存对应的内存中的数据已经被修改,而CPU本身并不知道,它仍然认为Cache中的数据就是内存中的数据,那在以后访问Cache映射的内存时,它仍然使用陈旧的Cache数据。这样就会发生Cache与内存之间数据“不一致性”的错误。