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

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第四十五章 注册字符类设备

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等


四十五 注册字符类设备

本章导读

在整个Linux设备驱动的学习中,字符设备驱动较为基础。本章将讲解注册字符类设备的编程方法。

45.1章节讲解了注册字符设备的基本步骤

45.2章节编写注册字符设备的驱动程序

45.3章节编写应用层测试程序

45.4章节编译驱动程序为驱动模块

本章内容对应视频讲解链接(在线观看):

注册字符类设备  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=16

程序源码在网盘资料“iTOP-STM32MP157开发板网盘资料汇总\09_嵌入式Linux开发指南(iTOP-STM32MP157)手册配套资料\驱动程序例程\08-注册字符类设备”路径下。

45.1 注册字符类设备简介

在Linux内核中,使用cdev结构体描述一个字符设备,cdev结构体的定义如下:

struct cdev {   //描述字符设备的一个结构体struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};

cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。使用下列宏可以从dev_t获得主设备号和次设备号:

而使用下列宏则可以通过主设备号和次设备号生成dev_t

MKDEV(int major, int minor)

cdev结构体的另一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。

Linux内核提供了一组函数以用于操作cdev结构体:

void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);

cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。

函数

void cdev_init(struct cdev *, const struct file_operations *);

第一个参数

要初始化的cdev

第二个参数

文件操作集 cdev->ops = fops; //实际就是把文件操作集写给ops

功能

cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operations之间的连接。

函数

int cdev_add(struct cdev *, dev_t, unsigned);

第一个参数

cdev的结构体指针

第二个参数

设备号

第三个参数

次设备号的数量

功能

cdev_add()函数用于动态申请一个cdev内存。

函数

void cdev_del(struct cdev *);

第一个参数

cdev的结构体指针

生成设备节点

字符设备注册完以后不会自动生成设备节点。我们需要使用mknod命令创建一个设备节点

格式:mknod 名称 类型 主设备号 次设备号

举例:

 mknod /dev/test c 247 0

/** @Descripttion: 注册字符设备*/
#include <linux/init.h>   //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/fs.h>     //包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations
#include <linux/kdev_t.h>
#include <linux/cdev.h>        // 对字符设备结构cdev以及一系列的操作函数的定义。包含了cdev 结构及相关函数的定义。
#define DEVICE_NUMBER 1        //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0  //定义次设备号的起始地址static int major_num, minor_num; //定义主设备号和次设备号struct cdev cdev;                      //定义一个cdev结构体
module_param(major_num, int, S_IRUSR); //驱动模块传入普通参数major_num
module_param(minor_num, int, S_IRUSR); //驱动模块传入普通参数minor_numint chrdev_open(struct inode *inode, struct file *file)
{printk("chrdev_open\n");return 0;
}
// file_operations chrdev_ops
struct file_operations chrdev_ops = {.owner = THIS_MODULE,.open = chrdev_open};static int hello_init(void)
{dev_t dev_num;int ret; //函数返回值if (major_num){/*静态注册设备号*/printk("major_num = %d\n", major_num); //打印传入进来的主设备号printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号dev_num = MKDEV(major_num, minor_num);                              //MKDEV将主设备号和次设备号合并为一个设备号ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //注册设备号if (ret < 0){printk("register_chrdev_region error\n");}printk("register_chrdev_region ok\n"); //静态注册设备号成功}else{/*动态注册设备号*/ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, 1, DEVICE_ANAME);if (ret < 0){printk("alloc_chrdev_region error\n");}printk("alloc_chrdev_region ok\n"); //动态注册设备号成功major_num = MAJOR(dev_num);            //将主设备号取出来minor_num = MINOR(dev_num);            //将次设备号取出来printk("major_num = %d\n", major_num); //打印传入进来的主设备号printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号}cdev.owner = THIS_MODULE;//cdev_init函数初始化cdev结构体成员变量cdev_init(&cdev, &chrdev_ops);//完成字符设备注册到内核cdev_add(&cdev, dev_num, DEVICE_NUMBER);return 0;
}static void hello_exit(void)
{unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); //注销设备号cdev_del(&cdev);printk("gooodbye! \n");
}
module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");

45.3 编写应用程序

编写应用程序如下所示:

#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;char buf[64] = {0};fd = open("/dev/test",O_RDWR);  //打开设备节点if(fd < 0){perror("open error \n");return fd;}//read(fd,buf,sizeof(buf)); //从文件中读取数据放入缓冲区中close(fd);return 0;
}

我们将app.c文件拷贝到Ubuntu的/home/nfs/11目录下,输入以下命令编译app.c,可以参考本手册“35.2.4设置交叉编译器”章节设置编译器,生成的app,如下所示:

arm-none-linux-gnueabihf-gcc -o app app.c

45.4 开发板实验

将刚刚45.2章节编写的驱动代码编译成模块。将上次编译chrdev.c的Makefile文件拷贝到chrdev.c同级目录下,文件如下图所示:

 

驱动编译成功如下图所示: 

驱动编译成功如下图所示: 

我们通过nfs将编译好的驱动程序加载模块,进入共享目录,加载驱动模块如下图所示:

insmod chrdev.ko

 

从上图可知,动态申请好了字符设备号,主设备号是239,次设备号是0。但是我们想要验证我们的字符设备是否注册成功,需要运行应用程序。我们输入“mknod /dev/test c 239 0”创建设备节点,然后再运行45.3章节编译好的APP应用程序,如下图所示: 

如上图所示,应用程序APP成功地打开了设备节点,说明字符设备注册成功。 


http://www.mrgr.cn/news/53760.html

相关文章:

  • 医院信息化与智能化系统(5)
  • 云原生后端(Cloud-Native Backend)
  • 网站分享丨UU在线工具
  • 【UML】一个UML学习的还不错的几个帖子
  • springBoot集成nacos注册中心以及配置中心
  • C++实现循环队列和链式队列操作(实验5--作业)
  • MySQL安装配置教程,入门学习攻略,重点知识点总结
  • 文字的力量
  • JAVA学习-练习试用Java实现“成绩归类”
  • 2024年十大优秀内部风险管理解决方案
  • 软考(网工)——Linux服务器配置
  • Redis 事务 主从复制
  • PRCV 2024 - Day 1
  • 大模型~合集12
  • 深入学习JPEG压缩原理与过程
  • 必看干货|等保测评(网络安全等级保护)五问五答
  • Java:数据结构-二叉树
  • 【Pycharm默认解释器配置文件】怎样删除配置解释器的无效历史记录?
  • uniapp和原生微信小程序的优劣、区别?
  • 在linux主机上用两台虚拟机(linux)实现虚拟串口通讯
  • 架构发展史
  • 如何有效保障专线健康:运维团队的专线监控策略
  • 推荐IDE中实用AI编程插件,目前无限次使用
  • 【服务器部署】Docker部署小程序
  • 基于SSM高校普法系统的设计
  • 什么是决策树