当前位置: 首页 > news >正文

linux驱动开发-地址映射

地址映射

在Linux驱动开发中,
地址映射是指如何将设备的物理地址映射到虚拟地址空间,
从而使得内核可以通过虚拟地址与设备进行交互。
这一过程在设备的初始化和操作中都是至关重要的,
尤其是在涉及到内存映射I/O时。

1. 地址映射的基本概念

物理地址:设备实际使用的物理内存地址。
虚拟地址:内核空间或用户空间中的地址,应用程序通过此地址来访问内存。
映射:将物理地址转换为虚拟地址的过程。

2. 映射的类型

在Linux中,主要有两种类型的地址映射:

直接映射:通过使用特定的函数将设备的物理地址直接映射到内核虚拟地址空间。
缓冲映射:这种映射涉及使用DMA(直接内存访问)来减少CPU的负担。

3. 主要函数

在Linux驱动程序中,常用的映射相关函数包括:

ioremap:用于将物理地址映射到内核虚拟地址空间。常用于设备内存和寄存器的访问。
void __iomem *ioremap(unsigned long offset, unsigned long size);ioremap_nocache:与ioremap类似,但不进行缓存,适用于要求实时性较高的设备。
void __iomem *ioremap_nocache(unsigned long offset, unsigned long size);iounmap:用于解除之前通过ioremap或ioremap_nocache创建的映射。
void iounmap(void __iomem *addr);

4. 映射步骤

获取物理地址:通常在设备树或PCI设备结构中获取设备的基地址。

调用ioremap:将设备的物理地址映射到虚拟地址空间。

访问设备寄存器:通过返回的虚拟地址访问设备的寄存器或缓冲区。

解除映射:在驱动卸载时调用iounmap释放之前的映射。

5. 注意事项

映射的地址范围需要在设备的地址空间内,超出范围可能导致未定义行为。

进行内存映射时,应该了解访问的内存区域是否需要特殊的同步操作,例如使用锁。

在多核处理器环境中,确保适时进行缓存同步,以避免数据不一致的问题。

示例

#include <linux/module.h>                // 包含内核模块相关的头文件
#include <linux/platform_device.h>       // 包含平台设备相关的头文件
#include <linux/io.h>                    // 包含内存映射I/O相关的头文件// 定义设备结构体
struct my_device {void __iomem *reg_base;             // 用于存储映射后的设备寄存器基地址
};// 驱动程序的probe函数,当设备被识别到时执行
static int my_device_probe(struct platform_device *pdev) {struct my_device *dev;               // 定义设备结构体指针resource_size_t addr;                // 用于存储设备的物理地址// 分配设备结构体的内存//kzalloc 是 Linux 内核中用于动态分配内存的一种函数,结合了内存分配和初始化的功能。它会在分配内存的同时将这块内存的所有字节初始化为零//devm_kzalloc 是 devres 内存管理系统的一种分配函数,它会在驱动程序卸载时自动释放分配的内存。//@param dev 指向设备结构体的指针//@param size 要分配的内存大小//@param flags 内存分配标志 GFP_KERNEL 表示分配内核内存,在可以睡眠的上下文中分配内存(适用于大多数内核线程和进程)。//                        ,GFP_ATOMIC 表示分配原子内存,在不可睡眠的上下文中分配内存,速度更快但可能会失败。dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);if (!dev)                            // 检查内存分配是否成功return -ENOMEM;                  // 分配失败返回内存不足错误// 这里假设获取物理地址的代码在此(具体实现依赖于设备)addr = /* 获取物理地址 */;// 将物理地址映射到内核虚拟地址空间//与 ioremap 不同,devm_ioremap 会在设备释放时自动解除映射,这样可以简化资源管理,减少内存泄漏的风险。
//@param dev 指向设备结构体的指针
//@param offset 设备的物理地址偏移量
//@param size 要映射的内存大小dev->reg_base = devm_ioremap(&pdev->dev, addr, /* 映射大小 */);if (!dev->reg_base)                  // 检查映射是否成功return -EIO;                     // 失败返回输入输出错误// 访问设备寄存器(以写入0x1为例)//`writel` 是 Linux 内核中用于写 32 位数据的函数,它会将传入的 32 位数据写入指定内存地址。//`dev->reg_base + OFFSET` 是设备寄存器的虚拟地址,可以通过偏移量访问。//`0x1` 是要写入的值。writel(0x1, dev->reg_base + OFFSET); // 使用偏移量访问寄存器// 保存设备私有数据到平台设备结构中以便后续操作//platform_set_drvdata 用于保存设备私有数据到平台设备结构中,以便后续操作。//`pdev` 是平台设备结构体的指针。//`dev` 是设备私有数据指针。platform_set_drvdata(pdev, dev);return 0;                            // probe函数返回0,表示成功
}// 驱动程序的remove函数,在驱动卸载时执行
static int my_device_remove(struct platform_device *pdev) {struct my_device *dev = platform_get_drvdata(pdev); // 获取设备私有数据/* 在这里添加驱动卸载时需要进行的清理操作 */return 0;                            // remove函数返回0,表示成功
}// 设备树支持,定义与设备匹配的ID表
static const struct of_device_id my_device_ids[] = {{ .compatible = "my_device" },      // 指定支持的设备名称{}
};MODULE_DEVICE_TABLE(of, my_device_ids); // 将设备ID表与内核模块注册// 定义平台驱动结构体
static struct platform_driver my_device_driver = {.probe = my_device_probe,            // 设备探测函数.remove = my_device_remove,          // 设备移除函数.driver = {.name = "my_device",             // 驱动名称.of_match_table = my_device_ids, // 设备树匹配表},
};// 注册平台驱动
module_platform_driver(my_device_driver); 
MODULE_LICENSE("GPL");                    // 声明模块为GPL许可证

LED的字符设备驱动

myled.h

#ifndef __MYLED_H__ // 防止头文件被重复包含
#define __MYLED_H__// 定义寄存器的地址
#define RCC_ADDR     0x50000a28 // RCC控制寄存器地址
#define GPIOE_MODER  0x50006000 // GPIOE模式寄存器地址
#define GPIOE_ODR    0x50006014 // GPIOE输出数据寄存器地址#endif // __MYLED_H__

myled.c

#include <linux/fs.h> // 包含文件系统相关的头文件
#include <linux/init.h> // 包含模块初始化/退出的头文件
#include <linux/module.h> // 包含模块相关的头文件
#include <linux/io.h> // 包含I/O映射相关的头文件
#include "myled.h" // 包含自定义的头文件#define CNAME "myled" // 定义字符设备的名字为 "myled"int major; // 存储主设备号
char kbuf[128] = { 0 }; // 定义一个字符缓冲区
unsigned int *rcc,*moder,*odr; // 保存对应寄存器的虚拟地址指针// 打开设备时调用的函数
int myled_open(struct inode* inode, struct file* file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 输出调试信息//地址映射rcc = ioremap(RCC_ADDR,4); // 将RCC_ADDR地址映射到内存if(rcc == NULL){pr_err("ioremap rcc error\n"); // 映射失败输出错误信息return -ENOMEM; // 返回错误码}moder = ioremap(GPIOE_MODER,4); // 映射GPIOE_MODER寄存器if(moder == NULL){pr_err("ioremap moder error\n"); // 映射失败输出错误信息return -ENOMEM; // 返回错误码}odr = ioremap(GPIOE_ODR,4); // 映射GPIOE_ODR寄存器if(odr == NULL){pr_err("ioremap odr error\n"); // 映射失败输出错误信息return -ENOMEM; // 返回错误码}//初始化led1熄灭*rcc |= (1<<4); // 使能GPIOE时钟*moder &= ~(3<<20); // 清空模式位*moder |= (1<<20); // 设置GPIOE为输出模式*odr &= ~(1<<10); // 将LED1熄灭return 0; // 打开设备成功
}// 从设备读取数据的函数
ssize_t myled_read(struct file* file,char __user* ubuf, size_t size, loff_t* offs)
{int ret;printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 输出调试信息if (size > sizeof(kbuf)) // 如果请求的大小大于kbuf的大小size = sizeof(kbuf); // 则将size设置为kbuf的大小ret = copy_to_user(ubuf, kbuf, size); // 将内核缓冲区内容拷贝到用户缓冲区if (ret) {pr_err("copy_to_user error\n"); // 拷贝失败输出错误信息return -EIO; // 返回输入输出错误}return size; // 返回拷贝的大小
}// 向设备写入数据的函数
ssize_t myled_write(struct file* file,const char __user* ubuf, size_t size, loff_t* offs)
{int ret;printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 输出调试信息if (size > sizeof(kbuf)) // 如果请求的大小超出kbuf的大小size = sizeof(kbuf); // 则调整大小ret = copy_from_user(kbuf, ubuf, size); // 从用户缓冲区拷贝数据到内核缓冲区if (ret) {pr_err("copy_from_user error\n"); // 拷贝失败输出错误信息return -EIO; // 返回输入输出错误}// kbuf[0]为1时点亮LED1,为0时熄灭LED1kbuf[0] == 1 ? (*odr |= (1<<10)) : (*odr &= ~(1<<10)); // 控制LED状态return size; // 返回写入的大小
}// 关闭设备时调用的函数
int myled_close(struct inode* inode, struct file* file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 输出调试信息iounmap(odr); // 解除对ODR寄存器的映射iounmap(moder); // 解除对MODER寄存器的映射iounmap(rcc); // 解除对RCC寄存器的映射return 0; // 关闭设备成功
}// 声明文件操作的结构体
const struct file_operations fops = {.open = myled_open, // 打开设备时调用的函数.read = myled_read, // 读取设备时调用的函数.write = myled_write, // 写入设备时调用的函数.release = myled_close, // 关闭设备时调用的函数
};// 模块初始化函数
static int __init myled_init(void)
{// 1.注册字符设备major = register_chrdev(0, CNAME, &fops); // 注册字符设备并获取主设备号if (major < 0) {pr_err("register_chrdev error\n"); // 注册失败输出错误信息return major; // 返回错误码}pr_info("register char device driver success. major = %d\n", major); // 注册成功输出信息return 0; // 返回成功
}// 模块退出函数
static void __exit myled_exit(void)
{// 2.注销字符设备unregister_chrdev(major, CNAME); // 注销字符设备
}// 指定模块的初始化和退出函数
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL"); // 模块许可证声明

test.c

#include <head.h> // 包含自定义的头文件int main(int argc, const char * argv[])
{int fd; // 文件描述符char buf[128] = {0}; // 定义缓冲区if((fd = open("/dev/myled", O_RDWR)) == -1) // 打开字符设备PRINT_ERR("open error"); // 打开失败输出错误信息while(1) { // 无限循环buf[0] = !buf[0]; // 切换buf[0]的值write(fd, buf, sizeof(buf)); // 写入数据到设备sleep(1); // 每隔1秒循环一次}close(fd); // 关闭设备return 0; // 返回成功
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • uniapp小程序,使用腾讯地图获取定位
  • Vue组件:依赖注入provide和inject的使用
  • Python中的单例模式:从入门到精通
  • 【秋招笔试】9.09阿里国际秋招(已改编)-三语言题解
  • Hadoop Pig
  • Vue3+setup+el-pagination+el-select封装下拉分页及懒加载
  • git:分支管理
  • 如果java垮了其他语言社区就遭殃了,这个说法可信吗?
  • ffmpeg编译连接报错 undefined reference to `uncompress‘
  • 安全工具 | 使用Burp Suite的10个小tips
  • CANFD和CAN最主要的区别
  • 查找算法--python
  • Qt 实现自定义截图工具
  • 二维码的原理以及Java生成二维码【中间带图片】
  • 计算机网络(四) —— 简单Tcp网络程序
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • Angular Elements 及其运作原理
  • CSS盒模型深入
  • Invalidate和postInvalidate的区别
  • Java多线程(4):使用线程池执行定时任务
  • PAT A1092
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • rc-form之最单纯情况
  • storm drpc实例
  • Travix是如何部署应用程序到Kubernetes上的
  • Webpack 4 学习01(基础配置)
  • webpack入门学习手记(二)
  • Windows Containers 大冒险: 容器网络
  • Xmanager 远程桌面 CentOS 7
  • 编写符合Python风格的对象
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 微服务框架lagom
  • 我感觉这是史上最牛的防sql注入方法类
  • 怎么将电脑中的声音录制成WAV格式
  • 走向全栈之MongoDB的使用
  • python最赚钱的4个方向,你最心动的是哪个?
  • ​油烟净化器电源安全,保障健康餐饮生活
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #systemverilog# 之 event region 和 timeslot 仿真调度(十)高层次视角看仿真调度事件的发生
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • $forceUpdate()函数
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (poj1.2.1)1970(筛选法模拟)
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (一)基于IDEA的JAVA基础12
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .env.development、.env.production、.env.staging
  • .NET Core 成都线下面基会拉开序幕