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

news/2024/12/23 10:41:17/文章来源:https://www.cnblogs.com/qq21497936/p/18623411

前言

  做过单片机的都知道,写驱动是直接代码设置和读取寄存器来控制外设实现基本的驱动功能,而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-genericPWD?=$(shell pwd)all:make -C $(KDIR) M=$(PWD) modulesclean: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 addstatic 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,需要使用内核库printkprintk("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 addstatic 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,需要使用内核库printkprintk("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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/857355.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频智能分析AI智能分析网关小知识:如何评估和提升视频监控系统的图像质量?

在数字化时代,视频监控系统已成为我们生活中不可或缺的一部分,无论是在公共安全、交通管理还是商业监控等领域,高质量的图像对于监控系统的效能至关重要。随着技术的发展,我们有了更多的工具和方法来评估和提升视频监控系统的图像质量。 本文将探讨如何通过一系列综合措施,…

麒麟系统离线安装部署tomcat

麒麟系统离线安装部署tomcat离线安装部署tomcat下载tomcat 要使用java 11 所以下载tomcat 9 下载链接使用sftp上传到服务器/data/install解压下载的包tar -zxvf apache-tomcat-9.0.98.tar.gz移动文件夹到指定目录mv /data/install/apache-tomcat-9.0.98 /opt/app/tomcat赋予权限…

windows下代理加速 CMD

在使用代理软件之后, 默认代理HTTP请求, 浏览器可以直接使用, 因此我们在浏览器中可以直接打开Google访问. 但是这个代理对于终端来说是不生效的, 这意味着我们需要安装npm包, 或者下载github的源码, 使用docker构建镜像时, 都可能会出现资源下载失败的问题. 为此, 我们需要给终…

简单的Bootstrap Tabs选项卡

这是一款非常简单的Bootstrap Tabs选项卡美化特效。该特效在原生Bootstrap Tabs选项卡的基础上,通过css3来对它进行了一些美化效果。在线预览 下载使用方法 在页面中引入jquery和bootstrap相关文件。<link href="path/to/css/bootstrap.min.css" rel="sty…

超详细教程!用看板软件搭建用户画像系统

通过以上步骤,你可以用看板搭建一个用户画像系统,并将其应用到实际业务中。请注意,这只是一个基本的框架和流程,具体实现时可能需要根据你的实际情况进行调整和优化。用看板搭建用户画像系统是一个综合性的过程,它涉及到数据的收集、处理、分析以及可视化展示等多个环节。…

震撼!冬至物流高峰下,哪些办公软件可增强部门联动?

一、前言 冬至期间,物流行业迎来汹涌的业务高峰,运输规划的高效性成为物流企业成功应对挑战的关键因素。在这个特殊时期,合适的可视化团队协作办公软件能够显著优化运输流程,提升整体运营效率。本文将从 J 人物流公司的视角出发,详细盘点 6 款此类办公软件,其中板栗看板将…

双旦节遇上管理软件,解锁团队协作与效率的双重密码

一、电商双旦节促销的挑战与复杂性 1.1 多任务、多团队协调 双旦节促销活动的策划与执行通常涉及多个部门的合作,包括产品团队、营销团队、客服团队、物流团队等。每个团队负责不同的任务,如产品选品、优惠券设计、广告投放、订单处理、客户服务等。各个团队在不同阶段需要进…

JavaWeb和SSM

JavaWeb 简单的登录和注册项目(日程管理第二期) 1、dao中定义的接口,提供操作名的标准,只看接口定义了哪些方法,这样和服务层的service的耦合度低。2、mysql中如果返回的是count()的结果,则类型默认为longMVCSession和Cookie 1、cookie是session的ID2、设置了时效性的Coo…

Qt编写机器码秘钥控制/日期防篡改/离线使用/硬件标识/运行时间/数量控制/批量更新秘钥

一、前言说明 搞软件开发一直追求精益求精的目标,从第一版的秘钥生成器到今天这个版本,经历了十年的时间,最初的版本做的非常简陋,就是搞了个异或加密,控制运行时间,后面又增加设备数量的控制,然后就是到期时间的限制。这种有个巨大缺陷就是可复制性,如果将授权的秘钥文…

反馈处理慢如蜗牛?优化流程的方法看这里

在电商领域,客户反馈的及时处理直接影响着复购率和品牌声誉。然而,很多团队都面临着反馈跟进流程混乱的问题:反馈记录杂乱无章,处理状态不透明,甚至责任人不明确。这种混乱不仅拖慢了问题解决的速度,还严重影响了客户的信任感。 如何打破这一局面?答案就在于团队文档管理…

【随手记录】Apache POI 报错:Zip bomb detected! The file would exceed the max. ratio of compressed file ....

1、背景:使用POI解析Excel报错:Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data。This may indicate that the file is used to inflate memory usage and this could pose a security risk.You can adj…