【Linux】基于驱动框架的程序编写测试

news2024/11/18 10:39:57

【Linux】基于驱动框架的程序编写测试

  • 字符设备驱动工作原理
  • ☆ 驱动程序开发
    • 驱动程序开发步骤
    • 驱动代码框架
    • 驱动框架设计流程
  • 编译与测试
    • 编译
    • 测试

参考博文:

【Linux】基于框架编写驱动代码、驱动代码编译和测试

Linux驱动(驱动程序开发、驱动框架代码编译和测试)

字符设备驱动工作原理

  字符设备驱动工作原理在linux的世界里一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。我们知道如果应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。Linux内核有那么多驱动程序,应用怎么才能精确的调用到底层的驱动程序呢?

补充:

  • 在Linux文件系统中,每个文件都用一个 struct inode结构体来描述,这个结构体记录了这个文件的所有信息,例如文件类型,访问权限等。
  • 在linux操作系统中,每个驱动程序在应用层的/dev目录或者其他如/sys目录下都会有一个文件与之对应
  • 在linux操作系统中, 每个驱动程序都有一个设备号
  • 在linux操作系统中,每打开一次文件,Linux操作系统会在VFS层分配一个struct file结构体来描述打开的文件

(1)当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。

(2) 根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。这里以字符设备为例。在Linux操作系统中每个字符设备都有一个struct cdev结构体。此结构体描述了字符设备所有信息,其中最重要的一项就是字符设备的操作函数接口

(3) 找到struct cdev结构体后,linux内核就会将struct cdev结构体所在的内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中的记录的函数操作接口地址记录在struct file结构体的f_ops成员中。

(4) 任务完成,VFS层会给应用返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层应用程序就可以通过fd找到struct file,然后在struct file找到操作字符设备的函数接口file_operation了。

其中,cdev_init和cdev_add在驱动程序的入口函数中就已经被调用,分别完成字符设备与file_operation函数操作接口的绑定,和将字符驱动注册到内核的工作。

字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系
在这里插入图片描述

  • 如图,在Linux内核中使用cdev结构体来描述字符设备,通过其成员 dev_t 来定义设备号(分为主、次设备号)以确定字符设备的唯一性。通过其成员 file_operations 来定义字符设备驱动提供给VFS的接口函数,如常见的open()、read()、write()等。

  • 在Linux字符设备驱动:

    • 模块加载函数通过 register_chrdev_region( )alloc_chrdev_region( )来静态或者动态获取设备号
    • 通过 cdev_init( ) 建立cdev与file_operations之间的连接
    • 通过 cdev_add( ) 向系统添加一个cdev以完成注册。
    • 模块卸载函数通过cdev_del( )来注销cdev
    • 通过unregister_chrdev_region( )来释放设备号。
  • 用户空间访问该设备的程序通过Linux系统调用,如open( )、read( )、write( ),用 file_operations 来定义字符设备驱动提供给VFS的接口函数。

☆ 驱动程序开发

驱动程序开发步骤

  Linux 内核就是由各种驱动组成的,驱动程序的编写一般都是弄清楚现有驱动程序的框架,并在这个框架中加入硬件

一般来说,编写一个 linux 设备驱动程序的大致流程如下:

  • 查看原理图、数据手册,了解设备的操作方法;
  • 在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始;
  • 实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序;
  • 设计所要实现的操作,比如 open、close、read、write 等函数;
  • 实现中断服务(中断并不是每个设备驱动所必须的);
  • 编译该驱动程序到内核中,或者用 insmod 命令加载;
  • 测试驱动程序;

下面就以一个简单的字符设备驱动框架代码来进行驱动程序的开发、编译等。

驱动代码框架

上层测试代码:

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
    int fd;
    char buf[1] = {'1'};

    fd = open("/dev/pin4",O_RDWR);
    if(fd < 0){
        printf("open error\n");
        perror("reason:");
    }
    else
        printf("open success\n");

    write(fd, buf, 1);
}

驱动框架代码

#include <linux/fs.h>			// 包含了文件系统相关的数据结构和函数		file_operations声明
#include <linux/init.h>			// 包含了模块初始化和清理函数的宏定义		__init  __exit 宏定义声明
#include <linux/module.h>		// 提供了Linux内核模块的基本函数和宏		module_init  module_exit声明
#include <linux/cdev.h>			// 定义了字符设备相关的结构和函数			cdev_init 字符设备初始化
#include <linux/device.h>		// 包含了设备类和设备的定义				class  devise声明
#include <linux/uaccess.h>		// 提供了用户空间和内核空间数据传输的函数	copy_from_user 的头文件
#include <linux/types.h>		// 提供了用户空间和内核空间数据传输的函数	设备号  dev_t 类型声明

//变量定义
static struct class  *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;             // 设备号
static int major = 231;         // 主设备号
static int minor = 0;           // 次设备号
static  char* module_name = "pin4"; // 模块名

// led_open 函数
static int pin4_open(struct inode *inode, struct file *file)
{
    printk("pin4 open\n");         //内核的打印函数用 printk
    return 0;
}

// led_write 函数
static int pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4 write\n");
    return 0;
}

// led_read 函数
static int pin4_read(struct file* file, char __user * buf, size_t count, loff_t *ppos)
{
    printk("pin4 read\n");
    return 0;
}

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
    .read  = pin4_read
};

// 真实驱动入口
int __init pin4_drv_init(void)    
{ 
    int ret;
    devno = MKDEV(major, minor);    // 2. 创建设备号
    //3. 注册驱动 告诉内核 把这个驱动加入到内核链表中
    ret = register_chrdev(major, module_name, &pin4_fops);  

    // 让代码在dev下自动生成设备 
    pin4_class = class_create(THIS_MODULE, "myfirstdemo");
    // 创建设备文件
    pin4_class_dev = device_create(pin4_class, NULL, devno, NULL, module_name);

    return 0;
}

void __exit pin4_drv_exit(void)
{
    device_destroy(pin4_class, devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);          //卸载驱动
}

module_init(pin4_drv_init);     //入口, 内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

驱动框架设计流程

  1. 确定主设备号
  2. 定义结构体 类型 file_operations
  3. 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
  4. 实现驱动入口:安装驱动程序时,就会去调用这个入口函数,执行工作:
    • file_operations 结构体告诉内核:注册驱动程序register_chrdev.
    • 创建类 class_create.
    • 创建设备 device_create.
  5. 实现出口:卸载驱动程序时,就会去调用这个出口函数,执行工作:
    • file_operations 结构体从内核注销:unregister_chrdev.
    • 销毁类 class_destroy.
    • 销毁设备结点 device_destroy.
  6. 其他完善:GPL协议、入口加载

1. 确定主设备、变量定义

//变量定义
static struct class  *pin4_class;		// 设备类
static struct device *pin4_class_dev;	// 设备文件

static dev_t devno;             		// 设备号
static int major = 231;         		// 主设备号
static int minor = 0;           		// 次设备号
static  char* module_name = "pin4"; 	// 模块名

2. 定义file_operations结构体,加载到内核驱动链表中

这是Linux内核中的file_operations 结构体


根据上层调用函数定义结构体成员:

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
    .read  = pin4_read
};

3. 实现结构体成员pin4_read等函数

// led_open 函数
static int pin4_open(struct inode *inode, struct file *file)
{
    printk("pin4 open\n");         //内核的打印函数用 printk
    return 0;
}

// led_write 函数
static int pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4 write\n");
    return 0;
}

// led_read 函数
static int pin4_read(struct file* file, char __user * buf, size_t count, loff_t *ppos)
{
    printk("pin4 read\n");
    return 0;
}

4. 驱动入口

// 真实驱动入口
int __init pin4_drv_init(void)    
{ 
    int ret;
    devno = MKDEV(major, minor);    // 2. 创建设备号
    //3. 注册驱动 告诉内核 把这个驱动加入到内核链表中
    ret = register_chrdev(major, module_name, &pin4_fops);  

    // 让代码在dev下自动生成设备 
    pin4_class = class_create(THIS_MODULE, "myfirstdemo");
    // 创建设备文件
    pin4_class_dev = device_create(pin4_class, NULL, devno, NULL, module_name);

    return 0;
}

其中

pin4_class=class_create(THIS_MODULE, "myfirstdemo");

由代码在/dev自动生成设备,除此之外还可以手动生成设备,在dev目录下

sudo mknod + 设备名字 + 设备类型(c表示字符设备驱动) + 主设备号 + 次设备号。
sudo mknod  pin0 c 8 1

5. 驱动出口

void __exit pin4_drv_exit(void)
{
    device_destroy(pin4_class, devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);          //卸载驱动
}

6、GPI协议,入口加载,出口加载

module_init(pin4_drv_init);     //入口, 内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

编译与测试

  模块的编译需要配置Linux平台的交叉编译工具链,配置Linux内核源码,其中关于交叉编译工具链的安装,Linux内核配置及编译请参考博文:

  • 1. 交叉编译工具链的安装及带wiringPi库的交叉编译实现
  • 2. Linux内核编译并移植至树莓派

注意:这里需要将编写的 pin4driver.c 放到 linux-rpi-4.14.y/drivers/char/ 下才能编译

编译

  • 在Makefile中添加编译成模块: 编译、连接后生成的内核模块后缀为.ko

  • 回到linux-rpi-4.14.y,编译驱动文件,模块编译指令:

    ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules 
    
  • 编译驱动模块成功会生成以下几个文件:

  • 最后将编译生成的驱动模块和编译好的测试文件拷贝至树莓派测试 scp

    scp drivers/char/pin4driver.ko pi@192.168.137.64:/home/pi
    
    scp pin4test pi@192.168.137.64:/home/pi
    

编译过程中,经历了这样的步骤: 先进入Linux内核所在的目录,并编译出pin4drive.o文件,运行MODPOST会生成临时的pin4drive.mod.c文件,而后根据此文件编译出pin4drive.mod.o,之后连接pin4drive.o和pin4drive.mod.o文件得到模块目标文件pin4drive.ko,最后离开Linux内核所在的目录。

测试

1. 加载内核驱动模块:

sudo insmod pin4driver.ko

  加载内核驱动(相当于通过insmod调用了module_init这个宏,然后将整个结构体加载到驱动链表中) 加载完成后就可以在dev下面看到名字为pin4的设备驱动


2. 查看内核驱动模块: 使用 lsmod 命令查看已加载的内核模块。


3. 添加访问权限:

sudo chmod 666 /dev/pin4

4.执行上层代码


5. 查看内核打印的信息

dmesg |grep pin4

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

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

相关文章

智能摄像头DIY教程

你要去度假&#xff0c;想看看家里的情况吗&#xff1f;你想了解人工智能和计算机视觉吗&#xff1f;你有 Raspberry Pi、网络摄像头和一些空闲时间吗&#xff1f;那么这个项目就是为你准备的&#xff01; 在本文中&#xff0c;我们将介绍如何使用 Raspberry Pi 在 Python 中创…

Matlab|考虑阶梯式碳交易与供需灵活双响应的综合能源系统优化调度

目录 1 主要内容 目标函数 模型&#xff1a; 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序方法复现《考虑阶梯式碳交易与供需灵活双响应的综合能源系统优化调度》&#xff0c;提出了供需灵活双响应机制&#xff0c;供应侧引入有机朗肯循环实现热电联产机组热电输…

MongoDB入门:安装及环境变量配置

一、安装MonggoDB Windows系统安装MongoDB 1、下载MongoDB安装包 访问MongoDB官方网站&#xff0c;选择与Windows系统相匹配的MongoDB Community Server版本进行下载。 Download MongoDB Community Server | MongoDB 2、安装MongoDB 双击下载好的安装包文件&#xff0c;根…

从Midjourney到秒画:探索国产AI绘图的崛起与未来

最近&#xff0c;许多人在询问&#xff1a; 是否有优秀的国产AI绘图产品&#xff1f; 如果让我推荐一款AI绘图工具&#xff0c;那毫无疑问是Midjourney。它在AI绘图领域的地位堪比OpenAI在人工智能领域的影响力&#xff0c;处于领先的水平。 不过&#xff0c;Midjourney的使用…

[Linux]僵尸进程,孤儿进程,环境变量

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;大大会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

Unity 查看Inspectors组件时严重掉帧

遇到一个问题&#xff0c;就是运行一个脚本的时候&#xff0c;只要我查看它的Inspectors&#xff0c;就会严重掉帧。 原本是30fps&#xff0c;只要查看这个组件&#xff0c;就掉到5fps。 这还真有点像波粒二象性&#xff0c;一观察就会掉帧&#xff0c;不观察就正常。 using…

【Ubuntu】minicom安装、配置、使用以及退出

目录 1 安装 2 配置 3 使用 4 退出 minicom是一个串口通信的工具&#xff0c;以root权限登录系统&#xff0c;可用来与串口设备通信。 1 安装 sudo apt-get install minicom 2 配置 使用如下命令进入配置界面&#xff1a; sudo minicon -s 进入配置界面后&#xff0c;…

Html2OpenXml:HTML转化为OpenXml的.Net库,轻松实现Html转为Word。

推荐一个开源库&#xff0c;轻松实现HTML转化为OpenXml。 01 项目简介 Html2OpenXml 是一个开源.Net库&#xff0c;旨在将简单或复杂的HTML内容转换为OpenXml组件。 该项目始于2009年&#xff0c;最初是为了将用户评论转换为Word文档而设计的 随着时间的推移&#xff0c;Ht…

人工智能技术在电磁场与微波技术专业的应用

在人工智能与计算电磁学的融合背景下&#xff0c;电磁学的研究和应用正在经历一场革命。计算电磁 学是研究电磁场和电磁波在不同介质中的传播、散射和辐射等问题的学科&#xff0c;它在通信、雷达、无 线能量传输等领域具有广泛的应用。随着人工智能技术的发展&#xff0c;这一…

清美项目 vue总结

vue绑定表单验证 <el-form ref"classform" :model"classform" :rules"classRules" label-width"80px"><el-form-item label"转入班级" prop"classId"><el-select v-model"classform.classId&…

HTML流光爱心

文章目录 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…

剩余电流继电器在轨道交通地铁车站的应用

0应用背景 城市轨道交通设备复杂、量大、分布广&#xff0c;在长期持续运行的过程中&#xff0c;存在潜在的火灾风险隐患。在国内外发生的地铁火灾事件中&#xff0c;电气原因引发的火灾占比最高&#xff0c;高达37%&#xff0c;其中&#xff0c;漏电流是重要因素。地铁车站电…

【网络】手动部署内网穿透(超详细教程)

一、环境搭建 本篇文章讲的是 服务器frp转发数据的方式 frp 下载&#xff1a;https://github.com/fatedier/frp/releases/tag/v0.58.1 如果无法访问githup&#xff0c;在如下连接下载一个加速器 Watt Toolkit 官网&#xff1a;https://steampp.net/ 下载完成以后&#xff0…

京东商品详情数据接口功能介绍?API接口介绍

京东商品详情数据接口是京东开放平台提供的一组应用程序编程接口&#xff08;API&#xff09;&#xff0c;允许开发者通过编程方式获取京东商城上特定商品的详细信息。这些接口为商家、第三方开发者以及消费者提供了丰富的数据支持&#xff0c;有助于提升电商平台的运营效率、用…

ODA(Open Design Alliance)试用小记-ODA提供源码下载就完全可控了吗?

1.概述 ODA(Open Design Alliance)库架构如下&#xff1a; 产品体系如下&#xff1a; ODA的产品体系越来越壮大&#xff0c;包括主流BIM格式SDK、Viewer、Cloud、数据交换等&#xff0c;每个模块需要单独购买&#xff0c;并提供“源码服务”。 2.是否可控&#xff1f; 值得…

Tensorflow2.0

Tensorflow2.0 有深度学习基础的建议直接看class3 class1 介绍 人工智能3学派 行为主义:基于控制论&#xff0c;构建感知-动作控制系统。(控制论&#xff0c;如平衡、行走、避障等自适应控制系统) 符号主义:基于算数逻辑表达式&#xff0c;求解问题时先把问题描述为表达式…

机器学习周报(9.23-9.29)

文章目录 摘要Abstract1 自监督学习&#xff08;Self-Supervised Learning&#xff09;1.1 BERT1.1.1 Masking Input1.1.2 Next Sentence Prediction1.1.3 BERT的使用方式 1.2 Why does BERT work?1.3 Multi-lingual BERT 2 pytorch中tensor相关函数学习使用2.1 张量拼接与拆分…

【Linux】磁盘分区挂载网络配置进程【更详细,带实操】

Linux全套讲解系列&#xff0c;参考视频-B站韩顺平&#xff0c;本文的讲解更为详细 目录 一、磁盘分区挂载 1、磁盘分区机制 2、增加磁盘应用实例 3、磁盘情况查询 4、磁盘实用指令 二、网络配置 1、NAT网络原理图 2、网络配置指令 3、网络配置实例 4、主机名和host…

二、初步编写drf API

2.1基于django #settings.py urlpatterns [path(admin/, admin.site.urls),path(auth,views.auth) #创建一个路由 ]#views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt# Create your views here.c…

一条sql在MySQL中是怎么执行的

目录 一、MySQL总体架构二、各层的作用1、连接层2、应用层3、存储引擎层 一、MySQL总体架构 作为常问八股文&#xff0c;相信不少小伙伴当年都被问到过这个问题&#xff0c;回答这个问题我们首先得知道MySQL服务器基本架构&#xff0c;主要分为连接层&#xff0c;应用层和存储…