Linux驱动开发笔记(七):操作系统MMU介绍,操作系统操作寄存器的原理和Demo

前言

做过单片机的都知道,写驱动是直接代码设置和读取寄存器来控制外设实现基本的驱动功能,而linux操作系统上是由MMU(内存管理单元)来控制,MMU实现了虚拟地址与芯片物理地址的对应,设置和获取MMU地址就是设置和获取映射的物理地址,从而跟单片机一样实现与物理硬件的驱动连接。

本篇就是描述了MMU的基本实现原理和Demo。

Demo

内存管理单元(简称MMU)

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系统(脚本,基本程序开发)
  • 熟悉linux烧写
  • 熟悉linux交叉编译
  • 熟悉linux文件系统制作和编译
  • 熟悉linux驱动编译
  • 熟悉linux物理地址映射
  • 熟悉linux一般开源库程序的编译移植(configre、make、make install)
  • 高级的makefile、系统编程等相关的就需要随着时间累积学习了
      概括起来,原来单片机就是直接操作寄存器,而linux需要通过内核的设备框架来注册设备驱动,驱动中用虚拟地址映射物理地址,通过写程序操作驱动虚拟机地址来实现操作物理地址。

概述

linux驱动中用虚拟地址映射物理地址,通过写程序操作驱动虚拟机地址来实现操作物理地址。

不出意外,内核提供了物理地址到虚拟地址的映射。

内核函数

头文件是:linux/uaccess.h(我们这是ubuntu,不是arm)

可以在内核根目录下搜索下:

shell 复制代码
find . -type f -exec grep -l "ioremap(phys" {} \; 

ioremap函数:把物理地址转换成虚拟地址

成功返回虚拟地址的首地址,失败返回NULL。(注意:同一物理地址只能被映射一次,多次映射会失败返回)。

c 复制代码
void __iomem *ioremap(phys_addr_t phys_addr, size_t size) 

简化下:

c 复制代码
void *ioremap(phys_addr_t phys_addr, size_t size); 

iounmap:释放掉ioremap映射的地址

c 复制代码
static inline void iounmap(void __iomem *addr) 

简化下:

c 复制代码
static void iounmap(void *addr) 

查看已经映射的物理地址

内核以物理地址的形式来管理设备资源,比如寄存器。这些地址保存在 /proc/iomem 。该设备列出了当前系统内存到物理设备的地址映射。

  • 第一列:显示每种不同类型内存使用的内存寄存器;
  • 第二列,列出这些寄存器中的内存类型,并显示系统RAM中内核使用的内存寄存器,若网络接口卡有多个以太网端口,则显示为每个端口分配的内存寄存器。
shell 复制代码
cat /proc/iomem

(注意:由于笔者是虚拟机,所以都是0吧)

驱动模板准备

首先复制之前的004_testReadWirte的驱动,改个名字为:005_testReadWritePhyAddr

shell 复制代码
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模块名称),模板准备好了

shell 复制代码
gedit Makefile 
c 复制代码
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文件的杂项设备名称:

shell 复制代码
gedit testReadWritePhyAddr.c
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"); ret = misc_register(&misc_dev); if(ret < 0) { printk("Failed to misc_register(&misc_dev)\n"); return -1; } return 0; } static void registerMiscDev_exit(void) { misc_deregister(&misc_dev); printk("bye-bye!!!\n"); } MODULE_LICENSE("GPL"); module_init(registerMiscDev_init); module_exit(registerMiscDev_exit); 

杂项设备驱动添加物理内存映射虚拟机内存操作Demo

步骤一:修改驱动write操作

c 复制代码
// 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; } 

步骤二:修改驱动read操作

c 复制代码
// 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; } printk("%s\n", kBuf); return 0; } 

步骤三:在程序中添加参数写入和读取

c 复制代码
// 读取
  ret = read(fd, buf, sizeof(buf) < 0); if(ret < 0) { printf("Failed to read %s\n", devPath); close(fd); return 0; }else{ printf("Succeed to read [%s]\n", buf); } // 修改内容 memset(buf, 0x00, sizeof(buf)); memcpy(buf, "Get you content", strlen("Get you content")); // 写入 ret = write(fd, buf, sizeof(buf)); if(ret < 0) { printf("Failed to write %s\n", devPath); close(fd); return 0; }else{ printf("Succeed to write [%s]\n", buf); } // 读取 ret = read(fd, buf, sizeof(buf) < 0); if(ret < 0) { printf("Failed to read %s\n", devPath); close(fd); return 0; }else{ printf("Succeed to read [%s]\n", buf); } 

使用gcc编译.c,输出默认是a.out。

步骤四:编译驱动

c 复制代码
make

步骤五:加载、卸载驱动查看输出

符合预期

Demo源码

驱动源码

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; } printk("%s\n", kBuf); 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"); ret = misc_register(&misc_dev); if(ret < 0) { printk("Failed to misc_register(&misc_dev)\n"); return -1; } return 0; } static void registerMiscDev_exit(void) { misc_deregister(&misc_dev); printk("bye-bye!!!\n"); } MODULE_LICENSE("GPL"); module_init(registerMiscDev_init); module_exit(registerMiscDev_exit); 

测试程序源码

c 复制代码
#include <stdio.h>
#include <unistd.h> #include <fcntl.h> int main(int argc, char **argv) { int fd = -1; char buf[32] = {0}; int ret = -1; const char devPath[] = "/dev/register_hongPangZi_testReadWrite"; fd = open(devPath, O_RDWR); if(fd < 0) { printf("Failed to open %s\n", devPath); return -1; }else{ printf("Succeed to open %s\n", devPath); } // 读取 ret = read(fd, buf, sizeof(buf) < 0); if(ret < 0) { printf("Failed to read %s\n", devPath); close(fd); return 0; }else{ printf("Succeed to read [%s]\n", buf); } // 修改内容 memset(buf, 0x00, sizeof(buf)); memcpy(buf, "Get you content", strlen("Get you content")); // 写入 ret = write(fd, buf, sizeof(buf)); if(ret < 0) { printf("Failed to write %s\n", devPath); close(fd); return 0; }else{ printf("Succeed to write [%s]\n", buf); } // 读取 ret = read(fd, buf, sizeof(buf) < 0); if(ret < 0) { printf("Failed to read %s\n", devPath); close(fd); return 0; }else{ printf("Succeed to read [%s]\n", buf); } close(fd); printf("exit\n"); fd = -1; return 0; }