第14章 内核空间与用户空间数据交互实验(iTOP-RK3568开发板驱动开发指南 )

news2024/12/28 17:37:56

在“第12章 字符设备驱动框架实验”中,已经对file_operations结构体的进行了填充,该结构体的每一个成员都对应着一个系统调用,例如read、write等,在对应的实验中,只是对调用函数进行了标志打印,并没有真正实现设备的读写功能,而在本章节将对内核空间与用户空间的数据交换功能进行实现。

14.1 内核空间与用户空间

Linux系统将可访问的内存空间分为了两个部分,一部分是内核空间,一部分是用户空间。操作系统和驱动程序运行在内核空间(内核态),应用程序运行在用户空间(用户态)。

那么为什么要区分用户空间和内核空间呢?

(1)内核空间中的代码控制了硬件资源,用户空间中的代码只能通过内核暴露的系统调用接口来使用系统中的硬件资源,这样的设计可以保证操作系统自身的安全性和稳定性。

(2)从另一方面来说,内核空间的代码更偏向于系统管理,而用户空间中的代码更偏重业务逻辑实现,俩者的分工不同。

硬件资源管理都是在内核空间完成的,应用程序无法直接对硬件进行操作,只能通过调用相应的内核接口来完成相应的操作。比如应用程序要对磁盘上的一个文件进行读取,应用程序可以向内核发起一个“系统调用”申请——我要读取磁盘上的文件。这个过程其实是通过一个特殊的指令让进程从用户态进入到了内核态。在内核空间中,CPU可以执行任何命令,包括从磁盘上读取数据,具体过程是先把数据读取到内核空间中,然后再把数据拷贝到用户空间并从内核态切换到用户态。此时应用程序已经从系统调用中返回并拿到了想要的数据,可以继续往下执行了。

进程只有从用户空间切换到内核空间才可以使用系统的硬件资源,切换的方式有三种:系统调用,软中断,硬中断,如下图(图 14-1)所示:

img

图 14-1

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

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

这俩个函数定义在了kernel/include/linux/uaccess.h文件下,如下所示:

copy_to_user

函数原型:

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

函数作用:

​ 把内核空间的数据复制到用户空间。

参数含义:

​ *to是用户空间的指针

​ *from是内核空间的指针

​ n是从内核空间向用户空间拷贝的字节数

copy_from_user

函数原型:

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

函数作用:

​ 把用户空间的数据复制到内核空间。

参数含义:

​ *to是内核空间的指针

​ *from是用户空间的指针

​ n是从用户空间向内核空间拷贝的字节数

14.3 实验程序编写

14.3.1 驱动程序编写

本驱动程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\09\module。

在该实验中将实现内核空间和用户空间进行数据交换的功能。以12章编写的字符设备驱动框架实验为基础编写驱动程序,程序使用copy_to_user函数和copy_from_user函数来实现内核空间和用户空间互传数据的功能,编写完成的file.c代码如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

static dev_t dev_num;  //设备号
static int major = 0;  //主设备号
static int minor = 0;  //次设备号
struct cdev cdev_test; // cdev

struct class *class;       //类
struct device *device;    //设备

/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_open\r\n");
    return 0;
}

/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    /*本章实验重点******/
    char kbuf[32] = {0};   //定义写入缓存区kbuf
    if (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 ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    /*本章实验重点******/
char kbuf[32] = "This is cdev_test_read!";//定义内核空间数据
// 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 int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_release\r\n");
    return 0;
}

/*设备操作函数,定义file_operations结构体类型的变量cdev_test_fops*/
struct file_operations cdev_test_fops = {
    .owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    .open = cdev_test_open, //将open字段指向chrdev_open(...)函数
    .read = cdev_test_read,  //将open字段指向chrdev_read(...)函数
    .write = cdev_test_write, //将open字段指向chrdev_write(...)函数
    .release = cdev_test_release, //将open字段指向chrdev_release(...)函数
};

static int __init chr_fops_init(void) //驱动入口函数
{
    /*注册字符设备驱动*/
    int ret;
/*1 创建设备号*/
//动态分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");     if (ret < 0)
    {
        printk("alloc_chrdev_region is error\n");//打印动态分配设备号失败
    }
    printk("alloc_chrdev_region is ok\n");

    major = MAJOR(dev_num); //获取主设备号
    minor = MINOR(dev_num); //获取次设备号

    printk("major is %d \r\n", major); //打印主设备号
    printk("minor is %d \r\n", minor); //打印次设备号
     /*2 初始化cdev*/
    cdev_test.owner = THIS_MODULE;
    cdev_init(&cdev_test, &cdev_test_fops);

    /*3 添加一个cdev,完成字符设备注册到内核*/
    cdev_add(&cdev_test, dev_num, 1);

    /*4 创建类*/
    class = class_create(THIS_MODULE, "test");

    /*5  创建设备*/
    device = device_create(class, NULL, dev_num, NULL, "test");
    return 0;
}

static void __exit chr_fops_exit(void) //驱动出口函数
{
    /*注销字符设备*/
    unregister_chrdev_region(dev_num, 1); //注销设备号
    cdev_del(&cdev_test);               //删除cdev
    device_destroy(class, dev_num);       //删除设备
    class_destroy(class);                 //删除类
}
module_init(chr_fops_init);   //注册入口函数
module_exit(chr_fops_exit);  //注册出口函数
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

以上代码在cdev_test_read函数中使用copy_to_user函数将内核数据拷贝到用户空间,在cdev_test_write函数中使用copy_from_user函数将用户空间数据拷贝到内核空间。

14.3.2 编写测试 APP

本应用程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\09\app。

编写测试APP其实是在编写Linux应用,编译完成的应用程序app.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}; //定义读取缓存区buf1
    char buf2[32] = "nihao"; //定义写入缓存区buf2
    fd = 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;
}

14.4 运行测试

14.4.1 编译驱动程序

在上一小节中的file.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下 所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m +=file.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放file.c和Makefile文件目录下,如下图(图14-2)所示:

img

图 14-2

然后使用命令“make”进行驱动的编译,编译完成如下图(图14-3)所示:

img

图14-3

编译完生成 file.ko目标文件,如下图(图 14-4)所示:

img

图 14-4

至此我们的驱动模块就编译成功了,下面进行应用程序编译.

14.4.2 编译应用程序

因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图(图 14-5)所示:

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

img

图 14-5

下面进行驱动程序的测试。

14.4.3 运行测试

驱动模块file.ko和测试程序app都已经准备好了,接下来就是运行测试。首先输入以下命令加载驱动程序,如下图(图14–6)所示:

insmod file.ko

img

图 14-6

输入以下命令运行应用程序,如下图(图 14-7)所示

img

图 14-7

由上图可知,打印“This is cdev_test_open”信息说明成功打开了字符设备驱动。

打印“ This is cdev_test_read”和“buf1 is This is cdev_test_read!”说明应用程序成功读取到内核的数据。

打印“This is cdev_test_write”和“kbuf is nihao”说明应用程序向内核写数据成功。

最后打印“This is cdev_test_release”说明卸载字符设备。

【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

This is cdev_test_write”和“kbuf is nihao”说明应用程序向内核写数据成功。

最后打印“This is cdev_test_release”说明卸载字符设备。

【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

2452613.11.2fec74a6elWNeA&id=669939423234

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

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

相关文章

Gradle下载库速度过慢解决办法

最近搞了个Gradle的项目&#xff0c;项目下载依赖库太慢了&#xff0c;于是… Gradle下载库速度过慢的问题可能由多种原因导致&#xff0c;以下是一些可能的解决方案&#xff1a; 1、使用国内镜像站点&#xff1a; 你可以改变Gradle的配置&#xff0c;使用国内的镜像站点来下…

如何在本地使用Docker搭建和运行Kubernetes集群

文章目录 1. 准备环境2. 安装Minikube3. 启动Minikube集群4. 验证集群5. 部署一个示例应用创建一个Deployment部署应用检查部署 6. 访问应用创建一个Service部署Service获取Service的访问地址 7. 清理资源结论 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN…

腾讯云PK阿里云2核2G云服务器租用价格表

2核2G云服务器可以选择阿里云服务器或腾讯云服务器&#xff0c;腾讯云轻量2核2G3M带宽服务器95元一年&#xff0c;阿里云轻量2核2G3M带宽优惠价108元一年&#xff0c;不只是轻量应用服务器&#xff0c;阿里云还可以选择ECS云服务器u1&#xff0c;腾讯云也可以选择CVM标准型S5云…

算法刷题记录-双指针/滑动窗口(LeetCode)

809. Expressive Words 思路 根据题目描述&#xff0c;我们可以知道&#xff0c;如果要将某个单词定义为可扩张&#xff08;stretchy&#xff09;&#xff0c;需要满足如下两个条件&#xff1a; 所以&#xff0c;我们在实现的时候&#xff0c;可以通过两个指针p1和p2&#x…

Reactor模型深度解析

文章目录 Reactor模型深度解析什么是Reactor模型Reactor模型的优势Reactor模型的实现方式同步IO异步IOselectpollepoll Reactor模型的应用场景总结 Reactor模型深度解析 什么是Reactor模型 Reactor模型是一种高并发IO编程模型&#xff0c;它的主要目的是简化IO编程的复杂性&a…

安装Python虚拟环境

环境 python3.10 Ubuntu22.04 首先设置默认python #查看python3 的安装位置 which python3 #设置默认python为 python3 sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 1 #查看是否成功 python --version创建虚拟环境 #安装venv sudo apt i…

【韩顺平】Linux基础

1.网络连接三种方式 1.1 桥接模式&#xff1a;虚拟系统可以和外部系统通讯&#xff0c;但是容易造成IP冲突【1-225】 1.2 NAT模式&#xff1a;网络地址转换模式。虚拟系统可以和外部系统通讯&#xff0c;不造成IP冲突。 1.3 主机模式&#xff1a;独立的系统。 2.虚拟机克隆 3…

Outlook无需API开发连接钉钉群机器人,实现新增会议日程自动发送群消息通知

Outlook用户使用场景&#xff1a; 在企业中&#xff0c;会议和活动的顺利举行对于业务运转和团队协作至关重要。然而&#xff0c;计划的变动总是无法避免&#xff0c;这可能会导致其他人的计划受到影响&#xff0c;打乱原有的安排。为了解决这个问题&#xff0c;许多企业开始使…

Leetcode131. 分割回文串

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 题解&#xff1a;力扣&#xff08…

xargs如何保留文本中的引号

如果文本中有引号&#xff0c;直接用xargs管道操作的话&#xff0c;引号会丢失&#xff0c;如下 该如何保留每一行文本中的引号呢&#xff0c;需要用到xargs的-d选项&#xff0c;设置一个分隔符&#xff0c;这里可以选用换行符来分割 顺便多来一条&#xff0c;直接将文本参数作…

亚马逊秋季促销指南——如何更好的利用促销?

最新消息&#xff0c;亚马逊官方宣布将会在10月份举行Prime会员大促&#xff0c;覆盖多个站点&#xff0c;亚马逊卖家们一定要抓住这波促销机会&#xff0c;在这个秋季再冲一把&#xff01;但是还有一些小白玩家可能对于亚马逊促销了解不够&#xff0c;那么接下来我要讲的这些准…

无涯教程-JavaScript - HEX2OCT函数

描述 HEX2OCT函数将十六进制数转换为八进制数。 语法 HEX2OCT (number, [places])争论 Argument描述Required/Optionalnumber 您要转换的十六进制数。 数字不能超过10个字符(40位)。数字的最高有效位是符号位。其余的39位是幅度位。 负数使用二进制补码表示。 Requiredpla…

微调chatGLM-6B大模型的方法

GLM官方的知乎微调教程&#xff1a;https://zhuanlan.zhihu.com/p/618498001 GLM官方的GitHub微调教程&#xff1a;https://github.com/THUDM/ChatGLM-6B/tree/main/ptuning 说这个微调出的模型和原模型是同时加载的&#xff0c;输入文本的时候会先看看微调出的模型有没有存相应…

服务器分析和监控

在当今数字化时代&#xff0c;对于网络流量的分析和监控变得越来越重要。本文将详细介绍如何利用HTTPS代理服务器来实现高效、安全且可靠的流量分析与监控功能&#xff0c;并提供具体操作步骤以及相关技巧。无论是企业需要优化网络性能还是个人用户&#xff0c;在遵循法规合规前…

E810 100G网卡和pcie bridge 总线带宽协商期望x16,结果X1,导致vpp性能不及预期

现象&#xff1a;pktgen 发包10000M/pps&#xff0c;vpp 运行在1&#xff0c;2&#xff0c;4&#xff0c;8&#xff0c;16 worker数时&#xff0c;数据包size&#xff08;64&#xff0c;256&#xff0c;512&#xff09;数据包转发量没有期望的差距量&#xff0c;且vpp接口rx-mi…

索尼 toio™应用创意开发征文|toio俄罗斯方块游戏

目录 引言 摘要 创意简述 准备工作&#xff5c;手工开始 代码编写&#xff5c;合理集成 使用体验&#xff5c;近乎奇妙 引言 索尼toio™编程机器人是一款引领技术创新的产品&#xff0c;为开发者提供了一个全新的编程和创造平台。toio™的设计旨在将技术、塑性和乐趣融为…

SV-6002T-P 网络对讲求助终端,立柱式智慧城市网络对讲求助终端,停车场出入口一键求助终端

SV-6002T-P 网络对讲求助终端&#xff0c;立柱式智慧城市网络对讲求助终端&#xff0c;停车场出入口一键求助终端 描述&#xff1a;SV-6002T是深圳锐科达电子有限公司的一款新型立柱型室外防水一键求助对讲终端&#xff0c;具有10/100M以太网接口&#xff0c;其接收网络的音频数…

全球研发中心城市专题协商会课题调研组莅临麒麟信安考察指导

9月7日上午&#xff0c;长沙市政协党组副书记、副主席石长松&#xff0c;市委统战部副部长、市工商联党组书记何惠风&#xff0c;市政协研究室主任郑志华&#xff0c;市工商联党组成员、副主席王婧等领导一行莅临麒麟信安开展全球研发中心城市专题协商会课题调研&#xff0c;麒…

由于 SIGPROF 信号,clone() 系统调用无限重启

最近遇到一个c 代码里调用system函数然后没返回的问题&#xff0c;此时调用进程所在CPU占用率100%。 通过 strace 和ftrace工具跟踪系统调用&#xff0c;以及查看内核源码&#xff0c;发现 此时&#xff0c;进程会一直不停的调用 clone系统调用&#xff0c;而 clone系统调用一…

Java项目-苍穹外卖-Day10-SpirngTask及WebSocket

文章目录 前言SpringTask介绍SpringTask_corn表达式Spring_Task入门案例 订单状态定时处理需求分析代码开发功能测试 前言 本章实现的业务功能 超时未支付订单自动取消&#xff0c;配送中订单商家忘点完成自动再固定时间检查且修改成完成状态 来单提醒功能 催单提醒功能 …