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

【Linux】用户向硬件寄存器写入值过程理解

思考一下,当我们咋用户态向寄存器写入一个值,这个过程是怎么样的呢?以下是应用程序通过标准库函数(如 write()ioctl() 或 mmap())向硬件寄存器写入值的详细过程,从用户空间到内核再到硬件的完整流程:


1. 用户空间(应用程序)

应用程序通过系统调用与内核交互,最终将数据传递给驱动。以下是三种常见方法的详细流程:


方法一:使用 ioctl() 系统调用

ioctl() 是最灵活的方式,适用于需要直接控制硬件寄存器的场景。

步骤:
  1. 打开设备文件
    应用程序通过 open() 系统调用打开设备文件(如 /dev/mydevice),获得文件描述符。

    int fd = open("/dev/mydevice", O_RDWR);
    
  2. 发送命令到内核
    使用 ioctl() 系统调用,将自定义命令和参数传递给内核驱动。例如,写入寄存器的命令可能定义为 MY_IOCTL_WRITE_REGISTER

    uint32_t value = 0x1234;
    ioctl(fd, MY_IOCTL_WRITE_REGISTER, &value); // 第三个参数是用户空间的指针
    
  3. 系统调用触发
    ioctl() 触发软中断(如 syscall 或 int 0x80),进入内核空间。


方法二:使用 write() 系统调用

write() 通常用于流式设备(如文件或网络),但某些驱动可能通过 write() 实现寄存器写入(需驱动支持)。

步骤:
  1. 打开设备文件

    int fd = open("/dev/mydevice", O_RDWR);
    
  2. 写入数据
    使用 write() 将数据写入设备文件。驱动需要解析写入的数据并映射到寄存器操作。

    uint32_t value = 0x1234;
    write(fd, &value, sizeof(value)); // 需要驱动支持将写入的数据解析为寄存器操作
    
  3. 系统调用触发
    write() 触发软中断,进入内核空间。


方法三:使用 mmap() 内存映射

mmap() 将物理地址直接映射到用户空间,允许直接访问寄存器,效率最高但需谨慎操作。

步骤:
  1. 打开设备文件

    int fd = open("/dev/mydevice", O_RDWR);
    
  2. 内存映射
    使用 mmap() 将设备寄存器的物理地址映射到用户空间的虚拟地址。

    void *reg_base = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    
  3. 直接写入寄存器
    通过映射后的指针直接操作寄存器。

    *(volatile uint32_t *)(reg_base + 0x100) = 0x1234; // 0x100 是寄存器偏移
    

2. 内核空间(驱动层)

内核驱动负责处理用户空间的请求,并最终将数据写入硬件寄存器。


步骤一:系统调用处理(以 ioctl 为例)
  1. 驱动的 ioctl 方法
    内核根据文件描述符找到对应的驱动,调用驱动的 ioctl 方法。驱动解析命令和参数,执行写寄存器操作。

    static long my_driver_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {switch (cmd) {case MY_IOCTL_WRITE_REGISTER:// 获取用户传递的值uint32_t value;if (copy_from_user(&value, (void __user *)arg, sizeof(value))) {return -EFAULT; // 复制失败}// 写入寄存器writel(value, reg_base + 0x100); // reg_base 是内核映射的寄存器基地址return 0;default:return -ENOTTY; // 不支持的命令}
    }
    
    Collapse
  2. 寄存器访问函数
    内核使用 writel()readl() 等原子操作函数(或直接通过指针)写入寄存器值。


步骤二:write() 系统调用处理
  1. 驱动的 write 方法
    驱动需要解析用户写入的数据,并映射到寄存器操作。
    static ssize_t my_driver_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {uint32_t value;if (copy_from_user(&value, buf, sizeof(value))) {return -EFAULT;}writel(value, reg_base + 0x100);return sizeof(value); // 返回写入的字节数
    }
    

步骤三:mmap() 内存映射处理
  1. 驱动的 mmap 方法
    驱动将物理地址映射到用户空间的虚拟地址。

    static int my_driver_mmap(struct file *filp, struct vm_area_struct *vma) {unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;unsigned long size = vma->vm_end - vma->vm_start;// 将物理地址映射到用户空间if (remap_pfn_range(vma, vma->vm_start,PHYSICAL_ADDR >> PAGE_SHIFT, // 物理地址转换为页帧号size, vma->vm_page_prot)) {return -EAGAIN;}return 0;
    }
    
    Collapse
  2. 物理地址映射
    驱动通过 ioremap() 将硬件寄存器的物理地址映射到内核虚拟地址空间:

    void __iomem *reg_base = ioremap(PHYSICAL_ADDR, 4096); // 4096 是映射区域大小
    

3. 硬件层

内核通过内存映射的地址直接访问硬件寄存器:

  1. 物理地址访问
    内核的 reg_base 是通过 ioremap() 映射的虚拟地址,对应硬件的物理地址。当内核执行 writel(value, reg_base + OFFSET) 时:

    • CPU 将虚拟地址转换为物理地址。
    • 通过总线(如 AXI、APB)将数据写入硬件寄存器。
  2. 硬件响应
    硬件检测到寄存器值变化后,根据寄存器的功能执行操作(如启动模块、配置时钟等)。


关键流程总结

阶段操作
用户空间1. 打开设备文件<br>2. 通过 ioctlwrite 或 mmap 发送请求
内核空间1. 处理系统调用(如 ioctlwrite)或内存映射(mmap)<br>2. 映射物理地址到内核虚拟地址<br>3. 执行寄存器写入操作
硬件层1. 通过总线接收数据<br>2. 更新寄存器值并触发硬件行为

关键函数与机制

  1. **ioremap()/ioremap_nocache()**:
    将硬件寄存器的物理地址映射到内核虚拟地址空间,允许内核直接访问。

  2. **writel()/readl()**:
    内核提供的原子操作函数,用于安全地读写寄存器(处理内存屏障等)。

  3. **copy_from_user()**:
    将用户空间的数据复制到内核空间(如 ioctl 和 write 中的参数传递)。

  4. **mmap() 和 remap_pfn_range()**:
    将物理地址映射到用户空间,允许用户直接访问寄存器。

  5. **ioctl() 和 write()**:
    用户空间与驱动通信的接口,通过自定义命令或数据流传递参数。


注意事项

  1. 权限控制

    • 设备文件(如 /dev/mydevice)需设置正确的权限(如 666 或 600)。
    • 驱动的 open 方法可进一步检查用户权限。
  2. 缓存一致性

    • 如果寄存器映射到缓存区域,需使用 volatile 关键字或 mb() 等内存屏障确保数据同步。
  3. 错误处理

    • 内核需检查 ioremapmmap 等操作是否成功,避免空指针。
    • 用户空间需处理 ioctlwrite 和 mmap 的返回值。

示例代码片段

驱动代码(my_driver.c
#include <linux/io.h>
#include <linux/uaccess.h>#define PHYSICAL_ADDR 0x40000000  // 硬件寄存器的物理地址
#define OFFSET 0x100              // 寄存器偏移static void __iomem *reg_base;// ioctl 处理函数
static long my_driver_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {switch (cmd) {case MY_IOCTL_WRITE_REGISTER:uint32_t value;if (copy_from_user(&value, (void __user *)arg, sizeof(value))) {return -EFAULT;}writel(value, reg_base + OFFSET);return 0;default:return -ENOTTY;}
}// write 处理函数
static ssize_t my_driver_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {uint32_t value;if (copy_from_user(&value, buf, sizeof(value))) {return -EFAULT;}writel(value, reg_base + OFFSET);return sizeof(value);
}// mmap 处理函数
static int my_driver_mmap(struct file *filp, struct vm_area_struct *vma) {unsigned long size = vma->vm_end - vma->vm_start;if (remap_pfn_range(vma, vma->vm_start,PHYSICAL_ADDR >> PAGE_SHIFT,size, vma->vm_page_prot)) {return -EAGAIN;}return 0;
}// 驱动初始化
static int __init my_driver_init(void) {reg_base = ioremap(PHYSICAL_ADDR, 4096);if (!reg_base) {pr_err("ioremap failed\n");return -ENOMEM;}// 注册字符设备...return 0;
}
module_init(my_driver_init);

Collapse

用户空间代码(user_app.c
#include <fcntl.h>
#include <sys/mman.h>int main() {int fd = open("/dev/mydevice", O_RDWR);if (fd < 0) {perror("open");return -1;}// 方法一:通过 ioctluint32_t value = 0x1234;ioctl(fd, MY_IOCTL_WRITE_REGISTER, &value);// 方法二:通过 writewrite(fd, &value, sizeof(value));// 方法三:通过 mmapvoid *reg_base = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (reg_base == MAP_FAILED) {perror("mmap");return -1;}*(volatile uint32_t *)(reg_base + 0x100) = 0x5678;close(fd);return 0;
}

Collapse


总结

应用程序通过以下方式将值写入硬件寄存器:

  1. **ioctl()**:通过自定义命令传递参数,驱动解析后写入寄存器。
  2. **write()**:驱动需解析写入的数据流,映射到寄存器操作。
  3. **mmap()**:直接映射物理地址到用户空间,高效但需谨慎操作。

内核通过 ioremap 映射物理地址,驱动通过原子操作函数(如 writel)确保安全访问,最终通过总线将数据写入硬件寄存器。

相关文章:

【Linux】用户向硬件寄存器写入值过程理解

思考一下&#xff0c;当我们咋用户态向寄存器写入一个值&#xff0c;这个过程是怎么样的呢&#xff1f;以下是应用程序通过标准库函数&#xff08;如 write()、ioctl() 或 mmap()&#xff09;向硬件寄存器写入值的详细过程&#xff0c;从用户空间到内核再到硬件的完整流程&…...

【Easylive】convertVideo2Ts 和 union 方法解析

【Easylive】项目常见问题解答&#xff08;自用&持续更新中…&#xff09; 汇总版 这两个方法是 transferVideoFile 中用于视频文件处理的核心辅助方法&#xff0c;下面我将结合它们在 transferVideoFile 中的使用场景进行详细解释。 1. convertVideo2Ts 方法解析 方法签…...

飞桨PP系列新成员PP-DocLayout开源,版面检测加速大模型数据构建,超百页文档图像一秒搞定

背景介绍 文档版面区域检测技术通过精准识别并定位文档中的标题、文本块、表格等元素及其空间布局关系&#xff0c;为后续文本分析构建结构化上下文&#xff0c;是文档图像智能处理流程的核心前置环节。随着大语言模型、文档多模态及RAG&#xff08;检索增强生成&#xff09;等…...

Java 锁机制详解:用“厕所门”和“防盗门”轻松理解多线程同步

Java 锁机制详解&#xff1a;用“厕所门”和“防盗门”轻松理解多线程同步 目录 锁的作用synchronized 关键字ReentrantLockReadWriteLockStampedLock避免死锁的诀窍总结与对比 锁的作用 生活中的例子&#xff1a;公共厕所一次只能进一人&#xff0c;门上的“有人/无人”标志…...

关于修改 vue Element admin、若依, 等后台管理系统模板的一些全局样式问题:

关于修改 vue Element admin、若依&#xff0c; 等后台管理系统模板的一些全局样式问题&#xff1a; 1、修改左侧菜单和顶部&#xff08;菜单&#xff09;的背景色、把背景色改为炫酷的背景图。 1&#xff09;上传图片 src/assets/images/menu-icon.png、 src/assets/images/…...

并发多线程八股

并发多线程 1.Java里面的线程和操作系统的线程一样吗&#xff1f;2.Java的线程安全在三个方面体现&#xff1a;3.保证数据一致性的方案4.线程创建的方式1&#xff09;Thread类2&#xff09;Runnable接口3&#xff09;Callable接口和FutureTask4&#xff09;线程池&#xff08;e…...

飞速(FS)HPC无损组网:驱动AI高性能计算网络转型升级

案例亮点 部署低功耗、高密度飞速&#xff08;FS&#xff09;以太网交换机&#xff0c;紧凑机身设计节省70%机房空间&#xff0c;冗余电源和智能风扇确保系统高可用性&#xff0c;有效优化散热和降低能耗。 支持25G/40G/100G多速率自适应交换架构&#xff0c;构建超低时延企业…...

Nest.js学习路径

作为前端开发工程师&#xff0c;系统学习Nest.js可以从以下步骤入手&#xff0c;结合其模块化架构、依赖注入和TypeScript特性&#xff0c;逐步掌握核心功能。以下是结合多个资源的综合学习路径&#xff1a; 1. 环境搭建与项目初始化 安装CLI工具 使用Nest.js官方CLI快速生成项…...

git 常用操作整理

一.git 的概念 Git 是一个分布式版本控制系统&#xff0c;用于跟踪文件的更改历史&#xff0c;帮助开发者管理代码的版本。以下是关于 Git 的一些基本概念&#xff1a; 1. 仓库&#xff08;Repository&#xff09; - **本地仓库**&#xff1a;在你的计算机上存储的项目文件及…...

JAVA数据库增删改查

格式 Main.java(测试类) package com.example;import com.example.dao.UserDao; import com.example.model.User;public class Main {public static void main(String[] args) {UserDao userDao new UserDao();// 测试添加用户System.out.println(" 添加用户 ");Us…...

上海某海外视频平台Android高级工程师视频一面

问的问题比较细&#xff0c;有很多小细节在里面&#xff0c;平时真不一定会注意到&#xff0c;做一个备忘&#xff1a; 1.Object类里面有哪些方法&#xff1f; Object 类是 Java 中所有类的根类&#xff0c;它定义了一些基本方法&#xff0c;供所有类继承和重写1. 常用方法 1…...

前后端数据序列化:从数组到字符串的旅程(附优化指南)

&#x1f310; 前后端数据序列化&#xff1a;从数组到字符串的旅程&#xff08;附优化指南&#xff09; &#x1f4dc; 背景&#xff1a;为何需要序列化&#xff1f; 在前后端分离架构中&#xff0c;复杂数据类型&#xff08;如数组、对象&#xff09;的传输常需序列化为字符…...

idea报错:程序包不存在

这里的程序包是我们项目里自己写的&#xff0c;idea却报错不存在。 解决方法: 参考这位大佬的方法&#xff0c;OK。...

【TVM教程】使用 TVMC Micro 执行微模型

Apache TVM是一个深度的深度学习编译框架&#xff0c;适用于 CPU、GPU 和各种机器学习加速芯片。更多 TVM 中文文档可访问 →https://tvm.hyper.ai/ 作者&#xff1a;Andrew Reusch, Mehrdad Hessar 本教程介绍如何用 C runtime 自动调优模型。 安装 microTVM Python 依赖项…...

spring boot 整合redis

1.在pom文件中添加spring-boot-starter-data-redis依赖启动器 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 2.编写三个实体类 RedisHash("p…...

进程间信号

进程间信号 信号的认识信号的产生进程对信号的处理机制普通信号的处理机制实时信号的处理机制 信号集操作函数信号的捕捉 信号的认识 信号的概念&#xff1a;  信号是一种软件中断&#xff0c;它用于通知进程一个异步事件的发生。  这些事件可能来自系统内部&#xff08;如硬…...

2011-2019年各省地方财政国债还本付息支出数据

2011-2019年各省地方财政国债还本付息支出数据 1、时间&#xff1a;2007-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区、年份、地方财政粮油物资储备管理等事务 4、范围&#xff1a;31省 5、指标说明&#xff1a;地方财政的国债…...

2025华为软件精英挑战赛2600w思路分享

这里写自定义目录标题 得分展示对象定义请求价值计算时间同步删除操作完整思路 得分展示 对象定义 // 将一个磁盘划分为多个基于标签聚合的区块 class Block{ public:int tag 0; // 区块标签int start_pos;int end_pos;int id;int use_size 0;int v;// 为区块确定范围Bloc…...

WEB安全-CTF中的PHP反序列化漏洞

什么是序列化&#xff1f; 简单来说序列化是将数组或对象转换成字符串的过程&#xff0c;这样的好处是利于对象存储与传输&#xff0c;在PHP中&#xff0c;序列化函数是serialize()&#xff0c;反序列化是unserialize() 无类序列化 无类序列化顾名思义就是不包含class的序列…...

【论文阅读】Co2l: Contrastive continual learning

原文链接&#xff1a;[2106.14413] Co$^2$L: Contrastive Continual Learning 阅读本文前&#xff0c;需要对持续学习的基本概念以及面临的问题有大致了解&#xff0c;可参考综述&#xff1a; Wang L, Zhang X, Su H, et al. A comprehensive survey of continual learning: …...

OpenMCU(五):STM32F103时钟树初始化分析

概述 本文主要描述了STM32F103初始化过程系统时钟的初始化,主要描述了系统时钟的初始化&#xff0c;AHB总线时钟&#xff0c;APB总线时钟等的初始化。 硬件板卡3d图 时钟树 STM32F103的时钟树&#xff0c;如下所示: 时钟源选择 从STM32F103的时钟树框图&#xff0c;我们可以…...

ISIS报文

IS-IS 报文 目录 IS-IS 报文 一、报文类型与功能 二、报文结构解析 三、核心功能特性 四、典型应用场景 五、抓包数据分析 六、总结 IS-IS&#xff08;中间系统到中间系统&#xff09;协议报文是用于链路状态路由协议中网络设备间交换路由信息的关键载体&#xff0c;其设…...

【Android】BluetoothSocket.connect () 的实现与协议栈交互源码解析

本文以 Android 蓝牙框架中的BluetoothSocket.connect()方法为切入点,深入剖析 Android 设备与远程蓝牙设备建立连接的全流程。从 Java 层的 API 调用出发,逐步追踪至 JNI 层的接口转发,最终进入 Buedroid 协议栈(RFCOMM/L2CAP 层),揭示蓝牙连接的核心机制。重点解析了权…...

首屏加载时间优化解决

&#x1f916; 作者简介&#xff1a;水煮白菜王&#xff08;juejin/csdn同名&#xff09; &#xff0c;一位前端劝退师 &#x1f47b; &#x1f440; 文章专栏&#xff1a; 高德AMap专栏 &#xff0c;记录一下平时学习在博客写作中记录&#xff0c;总结出的一些开发技巧✍。 感…...

RabbitMQ--延迟队列事务消息分发

目录 1.延迟队列 1.1应用场景 1.2利用TTL死信队列模拟延迟队列存在的问题 1.3延迟队列插件 1.4常见面试题 2.事务 2.1配置事务管理器 3.消息分发 3.1概念 3.2应用场景 3.2.1限流 3.2.2负载均衡 1.延迟队列 延迟队列(Delayed Queue)&#xff0c;即消息被发送以后, 并…...

Linux服务器组建与管理

#!/bin/bash #判断是否是root用户if [ "$USER" ! "root" ]; then echo "不是root用户&#xff0c;无法进行安装操作" exit 1 fi#关闭防火墙systemctl stop firewalld && systemctl disable firewalld && echo "防火墙已经关…...

程序化广告行业(48/89):DSP与外部平台对接的关键要点解析

程序化广告行业&#xff08;48/89&#xff09;&#xff1a;DSP与外部平台对接的关键要点解析 大家好&#xff01;在之前的博客中&#xff0c;我们逐步深入了解了程序化广告行业的诸多知识。一直以来&#xff0c;我都希望能和大家一起在这个领域探索&#xff0c;不断进步&#…...

设计模式 Day 2:工厂方法模式(Factory Method Pattern)详解

继 Day 1 学习了单例模式之后&#xff0c;今天我们继续深入对象创建型设计模式——工厂方法模式&#xff08;Factory Method&#xff09;。工厂方法模式为对象创建提供了更大的灵活性和扩展性&#xff0c;是实际开发中使用频率极高的一种设计模式。 一方面&#xff0c;我们将简…...

自动驾驶浪潮下,HMI 设计如何保障安全与便捷?

自动驾驶系统与 HMI 设计的关联性 自动驾驶系统涵盖了一系列复杂的传感器技术、算法以及执行机构。从激光雷达、摄像头等环境感知传感器&#xff0c;到用于处理海量数据的人工智能算法&#xff0c;再到控制车辆行驶的动力与转向执行系统&#xff0c;各部分协同工作&#xff0c…...

瑞昱RTD2556QR显示器驱动芯片

一、概述 RTD2556QR芯片是由Realtek公司精心研发的一款高性能显示驱动芯片&#xff0c;专为满足现代显示设备对高分辨率、多功能接口及稳定性能的需求而设计。该芯片凭借其卓越的技术特性和广泛的应用领域&#xff0c;在显示驱动市场中占据重要地位。它集成了多种先进的功能模…...