驱动-内核空间和用户空间数据交换

内核空间与用户控件数据交换
前面了解的字符设备中对 file_operations 结构体的进行了填充, 该
结构体的每一个成员都对应着一个系统调用, 例如 read、 write 等, 在字符设备相关的文章中有实验过对
调用函数进行了标志打印, 并没有真正实现设备的读写功能。 这里需要实现的功能其实就是内核空间和用户控件之间的数据交换

文章目录

  • 理解概念:内核空间与用户空间
  • 参考资料
  • 用户空间和内核空间数据交换
    • 用户空间数据复制到内核空间 copy_from_user
    • 数据从内核空间拷贝到用户空间copy_to_user
  • 实验
    • 源码程序 file.c
      • 方法 copy_to_user
      • 方法 copy_from_user
    • 编译文件 Makefile
    • 测试程序
        • 源码程序分析
    • 加载驱动 insmod file.ko
    • 执行程序 ./appfile
  • 总结


理解概念:内核空间与用户空间

Linux 系统将可访问的内存空间分为了两个部分, 一部分是内核空间, 一部分是用户空间。
操作系统和驱动程序运行在内核空间(内核态) , 应用程序运行在用户空间(用户态) 。
那么为什么要区分用户空间和内核空间呢?
(1) 内核空间中的代码控制了硬件资源, 用户空间中的代码只能通过内核暴露的系统调
用接口来使用系统中的硬件资源, 这样的设计可以保证操作系统自身的安全性和稳定性。
(2) 从另一方面来说, 内核空间的代码更偏向于系统管理, 而用户空间中的代码更偏重
业务逻辑实现, 俩者的分工不同。
硬件资源管理都是在内核空间完成的, 应用程序无法直接对硬件进行操作, 只能通过调用
相应的内核接口来完成相应的操作。 比如应用程序要对磁盘上的一个文件进行读取, 应用程序
可以向内核发起一个“系统调用” 申请——我要读取磁盘上的文件。 这个过程其实是通过一个
特殊的指令让进程从用户态进入到了内核态。 在内核空间中, CPU 可以执行任何命令, 包括
从磁盘上读取数据, 具体过程是先把数据读取到内核空间中, 然后再把数据拷贝到用户空间并
从内核态切换到用户态。 此时应用程序已经从系统调用中返回并拿到了想要的数据, 可以继续
往下执行了。
进程只有从用户空间切换到内核空间才可以使用系统的硬件资源, 切换的方式有三种: 系
统调用, 软中断, 硬中断, 如下图:
在这里插入图片描述

参考资料

字符设备的基础知识一定要了解,都是关联的知识点
申请字符设备号
注册字符设备
创建字符设备节点
字符设备驱动框架
杂项设备

用户空间和内核空间数据交换

内核空间和用户空间的内存是不能互相访问的。 但是很多应用程序都需要和内核进行数据
的交换, 例如应用程序使用 read 函数从驱动中读取数据, 使用 write 函数向驱动中写数据, 上
述功能就需要使用 copy_from_user 和 copy_to_user 俩个函数来完成。 copy_from_user 函数是将
用户空间的数据拷贝到内核空间。 copy_to_user 函数是将内核空间的数据拷贝到用户空间。
这俩个函数定义在了 kernel/include/linux/uaccess.h 文件下

用户空间数据复制到内核空间 copy_from_user

copy_from_user 用于将数据从用户空间复制到内核空间。

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

参数说明

  • to: 内核空间的目标地址
  • from: 用户空间的源地址
  • n: 要复制的字节

使用示例

char kernel_buf[BUFSIZE];
if (copy_from_user(kernel_buf, user_buf, count)) {// 处理错误return -EFAULT;
}

数据从内核空间拷贝到用户空间copy_to_user

copy_to_user 用于将数据从内核空间复制到用户空间。 函数原型如下:

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
  • to: 用户空间的目标地址
  • from: 内核空间的源地址
  • n: 要复制的字节数

实验

源码程序 file.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>static dev_t dev_num;//定义dev_t类型(32位大小)的变量dev_num
static int major;  //定义int类型的主设备号major
static int minor;//定义int类型的 次设备号minor
static struct cdev cdev_test;  //定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
struct device *device;    //设备/*打开设备函数*/
static int chrdev_open(struct inode *inode, struct file *file)
{printk("This is chrdev_open \n");return 0;
}static ssize_t chrdev_read(struct file *file,char __user *buf, size_t size, loff_t *off)
{/*本章实验重点******/char kbuf[32] = "This is read from kernel";//定义内核空间数据// copy_to_user:内核空间向用户空间传数据if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0)     {printk("copy_to_user error\r\n"); //打印copy_to_user函数执行失败return -1;}printk("This is cdev_test_read\r\n");return 0;
}/*向设备写入数据函数*/
static ssize_t chrdev_write(struct file *file,const char __user *buf,size_t size,loff_t *off)
{/*本章实验重点******/char kbuf[32] = {0};   //定义写入缓存区kbufif (copy_from_user(kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据{printk("copy_from_user error\r\n");//打印copy_from_user函数执行失败return -1;}printk("This is cdev_test_write\r\n");printk("kbuf is %s\r\n", kbuf);return 0;
}
static int chrdev_release(struct inode *inode, struct file *file)
{return 0;
}static struct file_operations cdev_test_ops = {.owner=THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = chrdev_open,//将open字段指向chrdev_open(...)函数.read = chrdev_read,//将open字段指向chrdev_read(...)函数.write = chrdev_write,//将open字段指向chrdev_write(...)函数.release = chrdev_release,//将open字段指向chrdev_release(...)函数
};//定义file_operations结构体类型的变量cdev_test_opsstatic int __init chrdev_fops_init(void)//驱动入口函数
{int ret;//定义int类型的变量ret,用来判断函数返回值ret=alloc_chrdev_region(&dev_num,0,1,"chardev_num"); //通过动态方式进行设备号注册if(ret < 0){printk("alloc_chrdev_region is error\n");}   printk("alloc_chrdev_region is ok\n");major=MAJOR(dev_num);//通过MAJOR()函数进行主设备号获取minor=MINOR(dev_num);//通过MINOR()函数进行次设备号获取printk("major is %d\n",major);printk("minor is %d\n",minor);使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体cdev_init(&cdev_test,&cdev_test_ops);cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块 ret= cdev_add(&cdev_test,dev_num,1);if(ret < 0 ){printk("cdev_add is error\n");}printk("cdev_add is ok\n");class_test  = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_testdevice_create(class_test,NULL,dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_testreturn 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{cdev_del(&cdev_test);//使用cdev_del()函数进行字符设备的删除unregister_chrdev_region(dev_num,1);//释放字符驱动设备号 device_destroy(class_test,dev_num);//删除创建的设备class_destroy(class_test);//删除创建的类printk("module exit \n");}
module_init(chrdev_fops_init);//注册入口函数
module_exit(chrdev_fops_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("wang fang chen "); //作者信息

源码分析:
这里省略之前内容关联知识的内容,重点看 内核空间和用户控件数据交换部分内容。

方法 copy_to_user

读取内核空间数据到用户空间,定义了内核空间数据,其实就是驱动程序里面定义了字节数组
char kbuf[32] = “This is read from kernel”;//定义内核空间数据

copy_to_user 方法调用后, copy kbuf 数据到 buf里面去,buf 在方法里面的参数是一个用户空间态指针,这样就传递到用户空间了。

static ssize_t chrdev_read(struct file *file,char __user *buf, size_t size, loff_t *off)
{/*本章实验重点******/char kbuf[32] = "This is read from kernel";//定义内核空间数据// copy_to_user:内核空间向用户空间传数据if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0)     {printk("copy_to_user error\r\n"); //打印copy_to_user函数执行失败return -1;}printk("This is cdev_test_read\r\n");return 0;
}

方法 copy_from_user

从用户空间数据copy 数据到内核空间
buf 数据是用户态指针类型,调用 copy_from_user 方法后 kbuf 就有新的数据了,其实其实驱动程序里面的数据就是内核态数据,然后将其打印出来。

/*向设备写入数据函数*/
static ssize_t chrdev_write(struct file *file,const char __user *buf,size_t size,loff_t *off)
{/*本章实验重点******/char kbuf[32] = {0};   //定义写入缓存区kbufif (copy_from_user(kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据{printk("copy_from_user error\r\n");//打印copy_from_user函数执行失败return -1;}printk("This is cdev_test_write\r\n");printk("kbuf is %s\r\n", kbuf);return 0;
}

编译文件 Makefile

#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += file.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean

测试程序

appfile.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) // 主函数
{int fd;                         // 定义 int 类型的文件描述符char buf1[32] = {0};            // 定义读取缓存区 buf1char buf2[32] = "nihao";        // 定义写入缓存区 buf2fd = open("/dev/test", O_RDWR); // 打开字符设备驱动if (fd < 0){perror("open error \n");return fd;}read(fd, buf1, sizeof(buf1));    // 从/dev/test 文件读取数据printf("buf1 is %s \r\n", buf1); // 打印读取的数据write(fd, buf2, sizeof(buf2));   // 向/dev/test 文件写入数据close(fd);return 0;
}
源码程序分析

这里关注read 方法,buf1 用户态数据,读取后 经过系统调用
copy_to_user(buf, kbuf, strlen(kbuf)),内核态数据copy 了一份到用户态 buf1 中,然后打印出来。

需要 aarch64-linux-gnu-gcc 来编译, 输入以下命令, 编译完成以后会生成一个可执行程序

aarch64-linux-gnu-gcc appfile.c -o appfile

生成可执行程序 appfile
在这里插入图片描述

加载驱动 insmod file.ko

这里都是以前的基本知识,暂不扩展
在这里插入图片描述

执行程序 ./appfile

结果如下: 不正是内核和用户态数据交互结果吗?
在这里插入图片描述

总结

  • copy_from_user 和 copy_to_user 是 Linux 内核中用于在用户空间和内核空间之间安全传输数据的两个重要函数。
  • 内核和用户态数据传递就是通过两个方法调用来实现,回调到用户态其实就是指针传递。

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

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

相关文章

5G_WiFi_CE_DFS

目录 一、规范要求 1、法规目录 2、定义 3、运行模式 4、主/从设备相关的运行行为及具体的动态频率选择&#xff08;DFS&#xff09;要求 5、产品角色确定测试项目 6、测试项目 测试项1&#xff1a;信道可用性检查&#xff08;Channel Availability Check&#xff09; …

Devops之GitOps:什么是Gitops,以及它有什么优势

GitOps 定义 GitOps 是一种基于版本控制系统&#xff08;如 Git&#xff09;的运维实践&#xff0c;将 Git 作为基础设施和应用程序的唯一事实来源。通过声明式配置&#xff0c;系统自动同步 Git 仓库中的期望状态到实际运行环境&#xff0c;实现持续交付和自动化运维。其核心…

【蓝桥杯】单片机设计与开发,第十二届

/*头文件声明区*/ #include <STC15F2K60S2.H>//单片机寄存器头文件 #include <init.h>//初始化底层驱动头文件 #include <led.h>//led,蜂鸣器,继电器底层驱动头文件 #include <key.h>//按键底层驱动头文件 #include <seg.h>//数码管底层驱动头…

Vue3连接MQTT作为客户端

先下载依赖 npx --yes --registry https://registry.npmmirror.com npm install mqtt 在src的api创建 mes.js // 导入axios import axios from axios;// 定义一个变量,记录公共的前缀, baseURL const baseURL http://localhost:8080; const instance axios.create({ base…

主服务器和子服务器之间通过NFS实现文件夹共享

背景&#xff1a; 子服务器想做一个备份服务器 但是之前有很多文件是上传到本地的&#xff0c;于是服务要从本地读取文件 但是在不在同一台服务器中&#xff0c;读取就会有问题&#xff0c;想 实现在两者之间创建一个共享文件夹 一 NFS挂载步骤&#xff1a; 在主服务器&#…

LeetCode算法题(Go语言实现)_39

题目 给定一个二叉树的根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 一、代码实现 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }func rightSideView(root *TreeNode) []int {i…

【AI提示词】长期主义助手提供规划支持

提示说明 长期主义是一种关注长期利益和持续学习的思维模式&#xff0c;帮助个人和组织在快速变化的环境中保持耐心和系统性思考。 提示词 # Role: Long-termist Assistant## Profile - language: 中文 - description: 长期主义是一种关注长期利益和持续学习的思维模式&…

数组 array

1、数组定义 是一种用于存储多个相同类型数据的存储模型。 2、数组格式 &#xff08;1&#xff09;数据类型[ ] 变量名&#xff08;比较常见这种格式&#xff09; 例如&#xff1a; int [ ] arr0&#xff0c;定义了一个int类型的数组&#xff0c;数组名是arr0&#xff1b; &am…

基于JavaAPIforKml实现Kml 2.2版本的全量解析实践-以两步路网站为例

目录 前言 一、关于两步路网站 1、相关功能 2、数据结构介绍 二、JAK的集成与实现 1、JAK类图简介 2、解析最外层数据 3、解析扩展元数据和样式 4、递归循环解析Feature 5、解析具体的数据 三、结论 前言 随着地理信息技术的快速发展&#xff0c;地理空间数据的共享…

脑科学与人工智能的交叉:未来智能科技的前沿与机遇

引言 随着科技的迅猛发展&#xff0c;脑科学与人工智能&#xff08;AI&#xff09;这两个看似独立的领域正在发生深刻的交汇。脑机接口、神经网络模型、智能机器人等前沿技术&#xff0c;正带来一场跨学科的革命。这种结合不仅推动了科技进步&#xff0c;也在医疗、教育、娱乐等…

3.1.3.2 Spring Boot使用Servlet组件

在Spring Boot应用中使用Servlet组件&#xff0c;可以通过注解和配置类两种方式注册Servlet。首先&#xff0c;通过WebServlet注解直接在Servlet类上定义URL模式&#xff0c;Spring Boot会自动注册该Servlet。其次&#xff0c;通过创建配置类&#xff0c;使用ServletRegistrati…

《AI大模型应知应会100篇》第10篇:大模型的涌现能力:为什么规模如此重要

第10篇&#xff1a;大模型的涌现能力&#xff1a;为什么规模如此重要 摘要 在人工智能领域&#xff0c;“规模"始终是大模型发展的核心关键词。随着参数量从百万级跃升至万亿级&#xff0c;大模型展现出令人惊叹的"涌现能力”&#xff1a;这些能力在小模型中几乎不可…

安宝特案例 | Fundació Puigvert 医院应用AR技术开创尿石症治疗新纪元

案例介绍 在医疗科技不断进步的今天&#xff0c;Fundaci Puigvert 医院迈出了重要一步&#xff0c;成功应用AR技术进行了全球首例同时使用两台内窥镜的ECIRS手术&#xff08;内镜肾内联合手术&#xff09;&#xff0c;由Esteban Emiliani M.D. PhD F.E.B.U 博士主刀。这标志着…

从数据海洋中“淘金”——数据挖掘的魔法与实践

从数据海洋中“淘金”——数据挖掘的魔法与实践 在这个数据飞速膨胀的时代&#xff0c;每天产生的数据量可以用“天文数字”来形容。如果将数据比作金矿&#xff0c;那么数据挖掘&#xff08;Data Mining&#xff09;就是在数据的海洋中挖掘黄金的技术。作为一门结合统计学、机…

kotlin的takeIf使用

takeIf用于判断指定对象是否满足条件&#xff0c;满足就返回该对象自身&#xff0c;不满足返回null。因为可以返回对象自身&#xff0c;所以可以用作链式调用&#xff0c;以简化代码&#xff0c;又因takeIf可能返回空&#xff0c;所以常常和let结合使用&#xff0c;示例如下&am…

[定位器]晶艺LA1823,4.5V~100V, 3.5A,替换MP9487,MP9486A,启烨科技

Features  4.5V to 100V Wide Input Range  3.5A Typical Peak Current Limit  Integrated 500mΩ low resistance high side power MOS.  Constant On Time Control with Constant Switching Frequency.  180μA Low Quiescent Current  150kHz/240kHz/420kHz Swi…

火山RTC 4 音视频引擎 IRTCVideo,及 音视频引擎事件回调接口 IRTCVideoEventHandler

一、IRTCVideo、IRTCVideoEventHandler 音视频引擎 IRTCVideo&#xff0c;及 音视频引擎事件回调接口 IRTCVideoEventHandler 负责音视频管理、创建房间/获得房间实例 1、创建引擎、及事件回调示例 如&#xff1a; void VideoConfigWidget::initRTCVideo() {m_handler.res…

前端获取不到后端新加的字段 解决方案

前端获取不到后端新加的字段 解决方案 sql 返回的是 FileInfo 对象 private String lastUpdateTimeStr;// 自定义 setLastUpdateTime 方法&#xff0c;确保在设置 lastUpdateTime 时自动格式化为字符串public void setLastUpdateTime(LocalDateTime lastUpdateTime) {this.las…

30天学Java第九天——线程

并行与并发的区别 并行是多核 CPU 上的多任务处理&#xff0c;多个任务在同一时间真正的同时执行并发是单核 CPU 上的多任务处理&#xff0c;多个任务在同一时间段内交替执行&#xff0c;通过时间片轮转实现交替执行&#xff0c;用于解决 IO 密集型任务的瓶颈 线程的创建方式…

论坛系统(测试报告)

文章目录 一、项目介绍二、设计测试用例三、自动化测试用例的部分展示用户名或密码错误登录成功编辑自己的帖子成功修改个人信息成功回复帖子信息成功 四、性能测试总结 一、项目介绍 本平台是用Java开发&#xff0c;基于SpringBoot、SpringMVC、MyBatis框架搭建的小型论坛系统…