做过单片机的都知道,写驱动是直接代码设置和读取寄存器来控制外设实现基本的驱动功能,而linux操作系统上是由MMU(内存管理单元)来控制,MMU实现了虚拟地址与芯片物理地址的对应,设置和获取MMU地址就是设置和获取映射的物理地址,从而跟单片机一样实现与物理硬件的驱动连接。 本篇就是描述了MMU的基本实现原理和Demo.
MMU是Memory Management Unit的缩写,中文名是内存管理单元,有时称作分页内存管理单元(英语:paged memory management unit,缩写为PMMU)。它是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件。 它的功能包括虚拟地址到物理地址的转换(即虚拟内存管理)、内存保护、中央处理器高速缓存的控制,在较为简单的计算机体系结构中,负责总线的仲裁以及存储体切换(bank switching,尤其是在8位的系统上)。 具体如何管理内存是比较专业的,还有很多方法,这些是内存管理相关的技术,但是我们写驱动,不需要接触这些,虚拟地址到物理地址的转换大致如下: 。
只需要知道如何映射/取消映射物理地址到虚拟地址即可,这样我们可以通过设置虚拟地址来实现设置芯片物理地址,通过获取虚拟机地址的数据来获取芯片物理地址的寄存器数据,这样就跟操作单片机一样,就是包了一层(这里写过单片机裸机直接操作寄存器跑的很容易理解)。 这里,试用虚拟机ubuntu,我们写2个驱动来,来用程序A写入一个数据到驱动A,A写入一个特定的物理地址d,B来读取特定的物理地址d从而获取到。(PS:此处,虚拟机,这么使用是有风险的,如果物理地址被其他程序映射使用了,就会导致它的数据在其他程序中的修改,在这里,我们主要是为了在虚拟机ubuntu上能够实现这个原理过程).
单片机开发跨入linux驱动开发:
linux驱动中用虚拟地址映射物理地址,通过写程序操作驱动虚拟机地址来实现操作物理地址。 不出意外,内核提供了物理地址到虚拟地址的映射.
头文件是:linux/uaccess.h(我们这是ubuntu,不是arm) 可以在内核根目录下搜索下:
find . -type f -exec grep -l "ioremap(phys" {} \;
成功返回虚拟地址的首地址,失败返回NULL。(注意:同一物理地址只能被映射一次,多次映射会失败返回)。 。
void __iomem *ioremap(phys_addr_t phys_addr, size_t size)
简化下:
void *ioremap(phys_addr_t phys_addr, size_t size);
static inline void iounmap(void __iomem *addr)
简化下:
static void iounmap(void *addr)
内核以物理地址的形式来管理设备资源,比如寄存器。这些地址保存在 /proc/iomem 。该设备列出了当前系统内存到物理设备的地址映射.
cat /proc/iomem
(注意:由于笔者是虚拟机,所以都是0吧) 。
首先复制之前的004_testReadWirte的驱动,改个名字为:005_testReadWritePhyAddr 。
cd ~/work/drive/ ls cp -arf 004_testReadWrite 005_testReadWritePhyAddr cd 005_testReadWritePhyAddr make clean ls mv testReadWrite.c testReadWritePhyAddr.c ls
修改makefile里面的模块名称(obj-m模块名称),模板准备好了 。
gedit Makefile
obj-m += testReadWritePhyAddr.o #KDIR:=/usr/src/linux-source-4.18.0/linux-source-4.18.0 KDIR:=/usr/src/linux-headers-4.18.0-15-generic PWD?=$(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: rm *.ko *.o *.order *.symvers *.mod.c
修改.c文件的杂项设备名称:
gedit testReadWritePhyAddr.c
#include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> // Demo_004 add static char kBuf[256] = {0x00}; // Demo_004 add // int (*open) (struct inode *, struct file *); int misc_open(struct inode * pInode, struct file * pFile) { printk("int misc_open(struct inode * pInode, struct file * pFile)\n"); memcpy(kBuf, "init kBuf", sizeof("init kBuf")); printk("kBuf = %s\n", kBuf); return 0; } // int (*release) (struct inode *, struct file *); int misc_release(struct inode * pInde, struct file * pFile) { printk("int misc_release(struct inode * pInde, struct file * pFile)\n"); return 0; } // ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft) { printk("ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)\n"); if(copy_to_user(pUser, kBuf, strlen(kBuf)) != 0) { printk("Failed to copy_to_user(pUser, kBuf, strlen(kBuf)\n"); return -1; } return 0; } // ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft) { printk("ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)\n"); if(copy_from_user(kBuf, pUser, size) != 0) { printk("Failed to copy_from_user(kBuf, pUser, size)\n"); return -1; } return 0; } struct file_operations misc_fops = { .owner = THIS_MODULE, .open = misc_open, .release = misc_release, .read = misc_read, .write = misc_write, }; struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, // 这个宏是动态分配次设备号,避免冲突 .name = "register_hongPangZi_testReadWritePhyAddr", // 设备节点名称 .fops = &misc_fops, // 这个变量记住,自己起的,步骤二使用 }; static int registerMiscDev_init(void) { int ret; // 在内核里面无法使用基础c库printf,需要使用内核库printk printk("Hello, I’m hongPangZi, registerMiscDev_init\n")