【Linux驱动】内核地址和物理地址

00. 目录

01. 设备驱动概述

直接操作寄存器点亮LED和通过驱动程序点亮LED最本质的区别就是有无使用操作系统。 有操作系统的存在则大大降低了应用软件与硬件平台的耦合度,它充当了我们硬件与应用软件之间的纽带, 使得应用软件只需要调用驱动程序接口API就可以让硬件去完成要求的开发,而应用软件则不需要关心硬件到底是如何工作的。 这将大大提高我们应用程序的可移植性和开发效率。

1.1 驱动的作用

设备驱动与底层硬件直接打交道,按照硬件设备的具体工作方式读写设备寄存器, 完成设备的轮询、中断处理、DMA通信,进行物理内存向虚拟内存的映射,最终使通信设备能够收发数据, 使显示设备能够显示文字和画面,使存储设备能够记录文件和数据。

在系统中没有操作系统的情况下,工程师可以根据硬件设备的特点自行定义接口,如对LED定义LightOn()、LightOff()等。 而在有操作系统的情况下,设备驱动的架构则由相应的操作系统定义,驱动工程师必须按照相应的架构设计设备驱动, 如在本次实验中必须设计file_operations的接口。这样,设备驱动才能良好地整合到操作系统的内核中。

1.2 有无操作系统的区别

1)无操作系统(即裸机)时的设备驱动 也就是直接操作寄存器的方式控制硬件,在这样的系统中,虽然不存在操作系统,但是设备驱动是必须存在的。 一般情况下,对每一种设备驱动都会定义为一个软件模块,包含.h文件和.c文件,前者定义该设备驱动的数据结构并声明外部函数, 后者进行设备驱动的具体实现。其他模块需要使用这个设备的时候,只需要包含设备驱动的头文件然后调用其中的外部接口函数即可。 这在STM32的开发中很常见,也相对比较简单。

2)有操作系统时的设备驱动 反观有操作系统时,首先,驱动硬件工作的的部分仍然是必不可少的,其次,我们还需要将设备驱动融入内核。 为了实现这种融合,必须在所有的设备驱动中设计面向操作系统内核的接口,这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备。

由此可见,当系统中存在操作系统的时候,设备驱动变成了连接硬件和内核的桥梁。操作系统的存在势必要求设备驱动附加更多的代码和功能, 把单一的驱动变成了操作系统内与硬件交互的模块,它对外呈现为操作系统的API。

操作系统的存在究竟带来了什么好处呢?

首先操作系统完成了多任务并发; 其次操作系统为我们提供了内存管理机制,32位Linux操作系统可以让每个进程都能独立访问4GB的内存空间; 对于应用程序来说,应用程序将可使用统一的系统调用接口来访问各种设备, 通过write()、read()等函数读写文件就可以访问各种字符设备和块设备,而不用管设备的具体类型和工作方式。

02. 内存管理单元MMU

在linux环境直接访问物理内存是很危险的,如果用户不小心修改了内存中的数据,很有可能造成错误甚至系统崩溃。 为了解决这些问题内核便引入了MMU,

2.1 MMU的功能

MMU为编程提供了方便统一的内存空间抽象,其实我们的程序中所写的变量地址是虚拟内存当中的地址, 倘若处理器想要访问这个地址的时候,MMU便会将此虚拟地址(Virtual Address)翻译成实际的物理地址(Physical Address), 之后处理器才去操作实际的物理地址。MMU是一个实际的硬件,并不是一个软件程序。他的主要作用是将虚拟地址翻译成真实的物理地址同时管理和保护内存, 不同的进程有各自的虚拟地址空间,某个进程中的程序不能修改另外一个进程所使用的物理地址,以此使得进程之间互不干扰,相互隔离。 而且我们可以使用虚拟地址空间的一段连续的地址去访问物理内存当中零散的大内存缓冲区。很多实时操作系统都可以运行在无MMU的CPU中, 比如uCOS、FreeRTOS、uCLinux,以前想CPU也运行linux系统必须要该CPU具备MMU,但现在Linux也可以在不带MMU的CPU中运行了。 总体而言MMU具有如下功能:

  • 保护内存: MMU给一些指定的内存块设置了读、写以及可执行的权限,这些权限存储在页表当中,MMU会检查CPU当前所处的是特权模式还是用户模式,如果和操作系统所设置的权限匹配则可以访问,如果CPU要访问一段虚拟地址,则将虚拟地址转换成物理地址,否则将产生异常,防止内存被恶意地修改。
  • 提供方便统一的内存空间抽象,实现虚拟地址到物理地址的转换: CPU可以运行在虚拟的内存当中,虚拟内存一般要比实际的物理内存大很多,使得CPU可以运行比较大的应用程序。

到底什么是虚拟地址什么是物理地址?

当没有启用MMU的时候,CPU在读取指令或者访问内存时便会将地址直接输出到芯片的引脚上,此地址直接被内存接收,这段地址称为物理地址, 如下图所示。

在这里插入图片描述

简单地说,物理地址就是内存单元的绝对地址,好比你电脑上插着一张8G的内存条,则第一个存储单元便是物理地址0x0000, 内存条的第6个存储单元便是0x0005,无论处理器怎样处理,物理地址都是它最终的访问的目标。

当CPU开启了MMU时,CPU发出的地址将被送入到MMU,被送入到MMU的这段地址称为虚拟地址, 之后MMU会根据去访问页表地址寄存器然后去内存中找到页表(假设只有一级页表)的条目,从而翻译出实际的物理地址, 如下图所示。

在这里插入图片描述

对于32位处理器而言,其虚拟地址空间共有4G(2^32),一旦CPU开启了MMU, 任何时候CPU发出的地址都是虚拟地址,为了实现虚拟地址到物理地址之间的映射, MMU内部有一个专门存放页表的页表地址寄存器,该寄存器存放着页表的具体位置, 用ioremap映射一段地址意味着使用户空间的一段地址关联到设备内存上, 这使得只要程序在被分配的虚拟地址范围内进行读写操作,实际上就是对设备(寄存器)的访问。

2.2 TLB的作用

讲到MMU我又忍不住和大家说下TLB(Translation Lookaside Buffer)的作用。 由上面的地址转换过程可知,当只有一级页表进行地址转换的时候,CPU每次读写数据都需要访问两次内存, 第一次是访问内存中的页表,第二次是根据页表找到真正需要读写数据的内存地址; 如果使用两级了表,那么CPU每次读写数据都需要访问3次内存,这样岂不是显得非常繁琐且耗费CPU的性能,

那有什么更好的解决办法呢?答案是肯定的,为了解决这个问题,TLB便孕育而生。 在CPU传出一个虚拟地址时,MMU最先访问TLB,假设TLB中包含可以直接转换此虚拟地址的地址描述符, 则会直接使用这个地址描述符检查权限和地址转换,如果TLB中没有这个地址描述符, MMU才会去访问页表并找到地址描述符之后进行权限检查和地址转换, 然后再将这个描述符填入到TLB中以便下次使用,实际上TLB并不是很大, 那TLB被填满了怎么办呢?如果TLB被填满,则会使用round-robin算法找到一个条目并覆盖此条目。

由于MMU非常复杂,在此我们不做过于深入的了解,大家只要大概知道它的作用即可, 感兴趣的同学可以到网上查阅相关资料,对于初学者,还是建议先掌握全局,然后再深挖其中重要的细节, 千万不能在汪洋大海中迷失了方向。本小结我们主要用到的是MMU的地址转换功能,在linux环境中, 我们开启了MMU之后想要读写具体的寄存器(物理地址),就必须用到物理地址到虚拟地址的转换函数。

03. 地址转换相关API

上面提到了物理地址到虚拟地址的转换函数。包括ioremap()地址映射和取消地址映射iounmap()函数。

在这里插入图片描述

3.1 ioremap函数

地址映射函数 (内核源码/arch/arc/mm/ioremap.c)

void __iomem *ioremap(phys_addr_t paddr, unsigned long size)
#define ioremap ioremap

函数参数和返回值如下:

参数:

  • paddr: 被映射的IO起始地址(物理地址);
  • size: 需要映射的空间大小,以字节为单位;

返回值: 一个指向__iomem类型的指针,当映射成功后便返回一段虚拟地址空间的起始地址,我们可以通过访问这段虚拟地址来实现实际物理地址的读写操作。

ioremap函数是依靠__ioremap函数来实现的,只是在__ioremap当中其最后一个要映射的I/O空间和权限有关的标志flag为0。 在使用ioremap函数将物理地址转换成虚拟地址之后,理论上我们便可以直接读写I/O内存,但是为了符合驱动的跨平台以及可移植性, 我们应该使用linux中指定的函数(如:iowrite8()、iowrite16()、iowrite32()、ioread8()、ioread16()、ioread32()等)去读写I/O内存, 而非直接通过映射后的指向虚拟地址的指针进行访问。读写I/O内存的函数如下:

unsigned int ioread8(void __iomem *addr)
unsigned int ioread16(void __iomem *addr)
unsigned int ioread32(void __iomem *addr)

void iowrite8(u8 b, void __iomem *addr)
void iowrite16(u16 b, void __iomem *addr)
void iowrite32(u32 b, void __iomem *addr)
  • 第1行:读取一个字节(8bit)
  • 第2行:读取一个字(16bit)
  • 第3行:读取一个双字(32bit)
  • 第5行:写入一个字节(8bit)
  • 第6行:写入一个字(16bit)
  • 第7行:写入一个双字(32bit)

对于读I/O而言,他们都只有一个__iomem类型指针的参数,指向被映射后的地址,返回值为读取到的数据据; 对于写I/O而言他们都有两个参数,第一个为要写入的数据,第二个参数为要写入的地址,返回值为空。 与这些函数相似的还有writeb、writew、writel、readb、readw、readl等, 在ARM架构下,writex(readx)函数与iowritex(ioreadx)有一些区别, writex(readx)不进行端序的检查,而iowritex(ioreadx)会进行端序的检查。

说了这么多,大家可能还是不太理解,那么我们来举个栗子,比如我们需要操作RGB灯中的蓝色led中的数据寄存器, 在51或者STM32当中我们是直接看手册查找对应的寄存器,然后往寄存器相应的位写入数据0或1便可以实现LED的亮灭(假设已配置好了输出模式以及上下拉等)。 前面我们在不带linux的环境下也是用的类似的方法,但是当我们在linux环境且开启了MMU之后, 我们就要将LED灯引脚对应的数据寄存器(物理地址)映射到程序的虚拟地址空间当中,然后我们就可以像操作寄存器一样去操作我们的虚拟地址啦!其具体代码如下所示。

unsigned long pa_dr = 0x20A8000 + 0x00;
unsigned int __iomem *va_dr;
unsigned int val;
va_dr = ioremap(pa_dr, 4);
val = ioread32(va_dr);
val &= ~(0x01 << 19);
iowrite32(val, va_dr);
  • 第1行:Address: Base address + 0h offset
  • 第2行:定义一个__iomem类型的指针
  • 第4行:将va_dr指针指向映射后的虚拟地址起始处,这段地址大小为4个字节
  • 第5行:读取被映射后虚拟地址的的数据,此地址的数据是实际数据寄存器(物理地址)的数据
  • 第7行:将蓝色LED灯引脚对应的位清零
  • 第8行:把修改后的值重新写入到被映射后的虚拟地址当中,实际是往寄存器中写入了数据

3.2 iounmap函数

iounmap函数定义如下:

取消地址映射函数 (内核源码/arch/arc/mm/ioremap.c)

void iounmap(void *addr)
#define iounmap iounmap

函数参数和返回值如下:

参数:

  • addr: 需要取消ioremap映射之后的起始地址(虚拟地址)。

返回值:

例如我们要取消一段被ioremap映射后的地址可以用下面的写法。

示例:

iounmap(va_dr);     //释放掉ioremap映射之后的起始地址(虚拟地址)

可以使用以下命令来查看。

[root@rk3399:/mnt/kernel/5th/4copy_from_user]# cat /proc/iomem 
00110000-0014ffff : persistent_ram
00150000-001cffff : persistent_ram
00200000-083fffff : System RAM
  00280000-0100ffff : Kernel code
  01110000-01457fff : Kernel data
0a200000-7fffffff : System RAM
fe300000-fe30ffff : /ethernet@fe300000
fe310000-fe313fff : /dwmmc@fe310000
fe320000-fe323fff : /dwmmc@fe320000
fe330000-fe33ffff : mmc1
fe380000-fe39ffff : /usb@fe380000
fe3a0000-fe3bffff : /usb@fe3a0000
fe3c0000-fe3dffff : /usb@fe3c0000
fe3e0000-fe3fffff : /usb@fe3e0000
fe800000-fe807fff : /usb0/dwc3@fe800000
  fe800000-fe807fff : /usb0/dwc3@fe800000
fe80c100-fe8fffff : /usb0/dwc3@fe800000
fe900000-fe907fff : /usb1/dwc3@fe900000
  fe900000-fe907fff : /usb1/dwc3@fe900000
fe90c100-fe9fffff : /usb1/dwc3@fe900000
fec00000-fecfffff : /dp@fec00000
ff100000-ff1000ff : /saradc@ff100000
ff110000-ff110fff : /i2c@ff110000
ff150000-ff150fff : /i2c@ff150000
ff180000-ff18001f : serial
ff260000-ff2600ff : /tsadc@ff260000
ff370000-ff37001f : serial
ff3c0000-ff3c0fff : /i2c@ff3c0000
ff3d0000-ff3d0fff : /i2c@ff3d0000
ff420030-ff42003f : /pwm@ff420030
ff630000-ff633fff : /dfi@ff630000
ff650000-ff6507ff : /vpu_service@ff650000
ff650800-ff65083f : /iommu@ff650800
ff660000-ff6603ff : /rkvdec@ff660000
ff660480-ff6604bf : /iommu@ff660480
ff6604c0-ff6604ff : /iommu@ff660480
ff680000-ff680fff : /rga@ff680000
ff690000-ff69007f : /efuse@ff690000
ff6d0000-ff6d3fff : /amba/dma-controller@ff6d0000
  ff6d0000-ff6d3fff : /amba/dma-controller@ff6d0000
ff6e0000-ff6e3fff : /amba/dma-controller@ff6e0000
  ff6e0000-ff6e3fff : /amba/dma-controller@ff6e0000
ff720000-ff7200ff : /pinctrl/gpio0@ff720000
ff730000-ff7300ff : /pinctrl/gpio1@ff730000
ff780000-ff7800ff : /pinctrl/gpio2@ff780000
ff788000-ff7880ff : /pinctrl/gpio3@ff788000
ff790000-ff7900ff : /pinctrl/gpio4@ff790000
ff7c0000-ff7fffff : /phy@ff7c0000
ff800000-ff83ffff : /phy@ff800000
ff848000-ff8480ff : /watchdog@ff848000
ff870000-ff870fff : /spdif@ff870000
ff880000-ff880fff : /i2s@ff880000
ff8a0000-ff8a0fff : /i2s@ff8a0000
ff8f0000-ff8f05ff : regs
ff8f1c00-ff8f1dff : cabc_lut
ff8f2000-ff8f23ff : gamma_lut
ff8f3f00-ff8f3fff : /iommu@ff8f3f00
ff900000-ff9005ff : regs
ff901c00-ff901dff : cabc_lut
ff902000-ff902fff : gamma_lut
ff903f00-ff903fff : /iommu@ff903f00
ff910000-ff913fff : /rkisp1@ff910000
ff914000-ff9140ff : /iommu@ff914000
ff915000-ff9150ff : /iommu@ff914000
ff940000-ff95ffff : /hdmi@ff940000
ff960000-ff967fff : /dsi@ff960000
ff9a0000-ff9affff : ff9a0000.gpu
[root@rk3399:/mnt/kernel/5th/4copy_from_user]# 

04. 预留

05. 附录

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值