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)
可以在内核根目录下搜索下:
find . -type f -exec grep -l "ioremap(phys" {} \;
ioremap函数:把物理地址转换成虚拟地址
成功返回虚拟地址的首地址,失败返回NULL。(注意:同一物理地址只能被映射一次,多次映射会失败返回)。
void __iomem *ioremap(phys_addr_t phys_addr, size_t size)
简化下:
void *ioremap(phys_addr_t phys_addr, size_t size);
iounmap:释放掉ioremap映射的地址
static inline void iounmap(void __iomem *addr)
简化下:
static void iounmap(void *addr)
查看已经映射的物理地址
内核以物理地址的形式来管理设备资源,比如寄存器。这些地址保存在 /proc/iomem 。该设备列出了当前系统内存到物理设备的地址映射。
- 第一列:显示每种不同类型内存使用的内存寄存器;
- 第二列,列出这些寄存器中的内存类型,并显示系统RAM中内核使用的内存寄存器,若网络接口卡有多个以太网端口,则显示为每个端口分配的内存寄存器。
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");
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操作
// 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操作
// 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;
}
步骤三:在程序中添加参数写入和读取
// 读取
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。
步骤四:编译驱动
make
步骤五:加载、卸载驱动查看输出
符合预期
Demo源码
驱动源码
#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);
测试程序源码
#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;
}
Linux驱动开发笔记(七):操作系统MMU介绍,操作系统操作寄存器的原理和Demo的更多相关文章
- 嵌入式linux驱动开发 笔记
@ 目录 首个驱动hellodrv 1.编写源码 2.编译模块 3.加载驱动 首个驱动hellodrv 3.如果下载不到,就自己编写,并编译驱动. 1.编写源码 2.编译模块 1.先写makefile ...
- iOS陆哥开发笔记(七) (AVFoundation简单介绍)
在AVFoundation框架中AVAudioRecorder类专门处理录音操作,支持多种音频格式. 以下是经常使用的属性和方法: 属性 说明 @property(readonly, getter=i ...
- linux驱动开发流程
嵌入式linux驱动开发流程嵌入式系统中,操作系统是通过各种驱动程序来驾驭硬件设备的.设备驱动程序是操作系统内核和硬件设备之间的接口,它为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个 ...
- linux 驱动学习笔记01--Linux 内核的编译
由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...
- 【转】linux驱动开发的经典书籍
原文网址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书 ...
- Linux驱动开发学习的一些必要步骤
1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...
- Linux 驱动开发
linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...
- Linux驱动开发必看详解神秘内核(完全转载)
Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入L ...
- linux驱动开发的经典书籍
转载于:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html 参加实习也近一个月了,严重感觉知识不够,真是后悔学校里浪费那么 ...
- 转:linux驱动开发的经典书籍
源地址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书为 ...
随机推荐
- LeetCode 218. 天际线问题 (扫描线+优先队列)
扫描线+优先队列 https://leetcode-cn.com/problems/the-skyline-problem/solution/tian-ji-xian-wen-ti-by-leetco ...
- FB 汉化版恢复慢速更新
汉化版将恢复慢速更新 -------------------------[Q群]------------------------ 1 群: 11675361 2 群: 47642719 ------- ...
- Laravel视图共享数据
Laravel视图共享数据 最近在用lavavel过程中想实现公共头部尾部需要的配置数据在所有的页面中都可以使用,便查看了官方文档,在此做一个总结: 一. 修改 ComposerServiceProv ...
- C#/.NET/.NET Core优秀项目和框架2024年9月简报
前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍.功能特点.使用方式以及部分功能截图 ...
- 关于 vue3 中的 fragment 组件
vue3 中的模板中只能返回一个元素 ,否则报错,使用 fragment 组件可以返回多个元素标签
- kotlin函数和Lambda表达式——>内联函数
1.内联函数 使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包.即那些 在函数体内会访问到的变量.内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销. 但是在许 ...
- RPC框架JMH测试-chatgpt自动生成
本文将介绍如何使用Java的JMH测试框架来测试RPC框架的性能.我们选择了Apache Dubbo作为目标RPC框架,Dubbo是一种高效的远程调用框架,它支持多种传输协议和序列化协议,并且具有很好 ...
- redis的CPA三进二原则
CAP C:consistency,数据在多个副本中能保持一致的状态. A:Availability,整个系统在任何时刻都能提供可用的服务,通常达到99.99%四个九可以称为高可用 P:Partiti ...
- transformer论文解读
1. 相关工作 2. 模型架构 3. 如何理解 Layer Norm 4. Encoder 和 Decoder 结构 5. 从 attention 到 Scaled Dot-Product Atten ...
- spring boot中使用quratz实现定时。 使用task直接调用的实现方法
1.定义工作类 2.创建配置类,将工作对象绑定到工作明细,然后创建触发器 与工作明细进行绑定 二.使用task实现定时任务 1.启动类上开启定时功能 2.在需要定时的任务上增加Schedule注解,并 ...