驱动的作用
设备驱动与底层硬件直接打交道,按照硬件设备的具体工作方式读写设备寄存器,完成设备的轮询、中断处理、DMA通信,进行物理内存向虚拟内存的映射,最终使通信设备能够接发数据,显示设备能够显示文字和画面,存储设备能够记录文件和数据。
内存管理单元MMU
MMU的功能
MMU为编程提供了方便统一的内存空间抽象,我们的程序中所写的变量地址是虚拟内存当中的地址,倘若处理器想要访问这个地址时,MMU便会将此虚拟地址翻译成实际的物理地址,之后处理器采取操作实际的物理地址。MMU是一个实际的硬件,并不是软件程序。
它的主要作用是将虚拟地址翻译成物理地址,同时管理和保护内存。不同的进程有各自的虚拟地址空间,进程之间不能相互修改各自所使用的物理地址,如此使得进程之间互不干扰,相互隔离。总体而言MMU具有以下功能:
- 保护内存: MMU给一些指定的内存块设置了读写以及可执行的权限,这些权限存储在页表中,MMU会检查CPU当前所处的模式是特权模式还是用户模式,如果和操作系统所设置的权限匹配则可以访问,如果CPU要访问一段虚拟地址,则将虚拟地址转换为物理地址,否则将产生异常,防止内存被恶意修改。
- 提供方便统一的内存空间抽象,实现虚拟地址到物理地址的转换: CPU可以运行在虚拟的内存中,虚拟内=内存一般要比实际的物理内存大得多,使得CPU可以运行比较大的应用程序。
什么是虚拟地址和物理地址
物理地址就是内存单元的绝对地址,如电脑上插着一张8G的内存条,则第一个存储单元便是物理地址0x0000,内存条的第六个存储单元便是0x0005,无论CPU怎么处理,物理地址都是它最终访问的目标。当没有启用MMU时,CPU在读取指令或者访问内存时会将地址直接输出到芯片的引脚上,此地址直接被内存接收,此时CPU访问的就是物理地址。
当CPU开启MMU时,CPU发出的地址将被送入到MMU,被送入到MMU的这段地址称为虚拟地址,之后MMU去访问页表地址寄存器,然后去内存中找到页表(假设只有一级页表)的条目,从而翻译出实际的物理地址。
TLB的作用
由上面的地址转换过程可知,当只有一级页表进行地址转换的时候,CPU每次读写数据都需要访问两次内存,第一次是访问内存中的页表,第二次是根据页表找到真正需要读写的内存地址。如果使用两级页表,那么CPU每次读写数据都需要访问3次内存,这样一来显得非常繁琐且耗费CPU的性能。为了解决这个问题TLB孕育而生。在CPU传出一个虚拟地址时,MMU最先访问TLB,假设TLB中包含可以直接转换此虚拟地址的地址描述符,则会直接使用这个地址描述符检查权限和地址转换,如果TLB中没有这个地址描述符,MMU才会去访问页表并找到地址描述符之后进行权限检查和地址转换,然后再将这个描述符填入到TLB中以便下次使用,实际上TLB并不是很大,如果TLB被填,则会使用round-robin算法找到一个条目并覆盖此条目。
fire_operations的open()函数的工作
container_of()
#define container_of(ptr,type,member)({ \
const typeof(((type *)0)->member)*__mptr = (ptr);\
(type *)((char *)__mptr - offsetof(type,member));})
- ptr: 结构体变量中某个成员的地址。
- type: 结构体类型
- member: 该结构体变量的具体名字
- retval: 结构体type的首地址
函数原理是通过已知类型type的成员member的地址ptr,计算出结构体type的首地址。type的首地址 = ptr - size,需要注意的是它们的大小都是以字节为单位计算的,container_of()函数的如下:
- 判断ptr与member是否为同一类型。
- 计算size大小,结构体的起始地址 = (type *)((char *)ptr - size)(注:强制转换为该结构体指针)
通过此函数我们便可以获取led_chrdev结构体的首地址。
private_data变量
一般很多的linux驱动都会将文件的私有数据private_data指向设备结构体,其保存了用户自定义设备结构体的地址。自定义结构体的地址被保存在private_data后,可以通过读写等操作该私有数据去访问设备结构体中的成员,这样做体现了linux中面向对象的程序设计思想。