Linux驱动开发-03字符设备驱动框架搭建

一、字符设备驱动开发步骤

  1. 驱动模块的加载和卸载(将驱动编译模块,insmod加载驱动运行)
  2. 字符设备注册与注销(我们的驱动实际上是去操作底层的硬件,所以需要向系统注册一个设备,告诉Linux系统,我有这个设备,当驱动模块加载的时候我们去注册设备,当驱动模块卸载的时候我们注销设备)
  3. 实现设备的具体操作函数(我们注册完设备后,需要对设备进行读写操作,是通过一个结构体来进行的)
  4. 添加LICENSE 和作者信息

通过上面的字符设备的驱动开发步骤的了解,其实我们只需要掌握每一种设备的驱动框架按照框架去进行开发就行

 1.1 设备号

        Linux中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。 Linux提供了一个名为 dev_t的数据类型表示设备号

//设备号数据类型在内核中的定义
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
typedef unsigned int __u32;
//可以看到设备号就是一个u32的数据类型

         32位的数据构成了主设备号和次设备号两部分,其中高12位为主设备号, 低 20位为次设备号。因此 Linux系统中主设备号范围为 0~4095

 1.2 设备号的分配

(1)静态分配设备号

使用“ cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号,选择一个没有被使用的设备号进行使用即可

(2)动态分配设备号

设备号申请函数和设备号释放函数

/*
* alloc_chrdev_region() - register a range of char device numbers* @dev: output parameter for first assigned number* @baseminor: first of the requested range of minor numbers* @count: the number of minor numbers required* @name: the name of the associated device or driver
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
/** __register_chrdev() - create and register a cdev occupying a range of minors* @major: major device number or 0 for dynamic allocation* @baseminor: first of the requested range of minor numbers* @count: the number of minor numbers required* @name: name of this range of devices* @fops: file operations associated with this devices
*/
int __register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name,const struct file_operations *fops)

 1.3 file_operations

file_operations结构体就是设备的具体操作函数

struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*mremap)(struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endif
};

二、实战之驱动文件的编写

2.1 应用程序与驱动程序的交互方式

Linux 应用程序对驱动程序的调用

         在Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

        应用程序运行在用户空间,而Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。

应用程序运行在用户空间,驱动是属于内核的一部分;我们的应用程序想对内核空间进行操作,就需要通过系统调用的方式(例如下面用户空间的open就需要通过系统调用,来通过file_operations结构体里面的open对设备进行具体)

2.2 驱动中的open、close、read、release函数介绍

/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int chrdevbase_open(struct inode *inode, struct file *filp);/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt);/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt);/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int chrdevbase_release(struct inode *inode, struct file *filp);

2.3 内核区与用户区数据交互 

驱动不能直接访问应用程序的数据,应用程序也不能直接访问内核数据,必须借助其他函数

unsigned long copy_from_user(void *to, const void *from, unsigned long n);
//将用户空间的数据复制到内核空间unsigned long copy_to_user(void *to, const void *from, unsigned long n);
//将内核空间的数据复制到用户空间

 所以,我们的内核read的时候,应该使用copy_to_user;内核write的时候,应该使用copy_from_user;

 2.4 chrdevbase.c和 chrdevbaseAPP.c

chrdevbase.c

#include <linux/init.h> //包含宏定义的头文件(printk的头文件)
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/fs.h>//注册设备和注销设备的头文件
#include<linux/kernel.h>
#include <linux/ide.h>
#include <linux/types.h>#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME  "chrdevbase"//设备名字/*驱动不能直接访问应用程序的数据,应用程序也不能直接访问内核数据,必须借助其他函数(copy_from_user\copy_to_user)*//*我们的应用程序需要从驱动中去读取数据,并且还要向驱动写数据*/static char kenelreadbuf[100],kenelwritebuf[100];//我们驱动的读写缓冲区,file_operation结构体就是操作这两个static char keneldata[]={"keneldata data!"};//内核的数据,我们驱动read的时候就是把这个字符串返给应用程序//从设备读数据
static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos){
/*参数解释:
filp:要打开的设备文件(文件描述符),也就是驱动文件的fd,因为我们是应用程序去read驱动的数据
buf:发送的数据就发送到buf里面去(我们的应用程序提供了一个buf,我们的驱动程序写到那个buf里面去)
count:驱动程序需要向应用程序发送多少个字节的数据
*/int ret=0;memcpy(kenelreadbuf,keneldata,sizeof(keneldata));//把keneldata中的数据拷贝到kenelreadbuf中,最后一个参数是拷贝的长度ret=copy_to_user(buf,kenelreadbuf,count);//把内核数据给应用程序,函数中的buf和count都是由应用程序决定,返回值为0表示成功if(ret == 0){printk("kernel send data :%s\r\n",kenelreadbuf);}else{printk("kernel send data failed!\r\n");}return 0;
}
//向设备写数据(应用程序把数据写给驱动程序)
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos){int ret=0;ret=copy_from_user(kenelwritebuf,buf,count);//把应用程序的数据给驱动,函数中的buf和count都是由应用程序决定if(ret == 0){printk("kernel receive data :%s\r\n",kenelwritebuf);}else{printk("kernel receive data failed!\r\n");}return 0;
}
//打开设备
static int chrdevbase_open(struct inode *inode, struct file *filp){return 0;
}
//关闭设备
static int chrdevbase_close(struct inode *inode, struct file *filp){return 0;
}
/*
字符设备:操作集合
*/
static const struct file_operations chrdevbase_fops = {.owner	= THIS_MODULE,//owner拥有该结构体的模块的指针,一般设置为 THIS_MODULE.read	= chrdevbase_read,.write	= chrdevbase_write,.open	= chrdevbase_open,.release= chrdevbase_close,
};//驱动入口函数
static int __init chrdevbase_init(void)
{int ret=0;/*注册字符设备驱动函数:主设备号、设备名字、file_operations结构体*/ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);//使用这个函数注册时,会把当前主设备号下的所有次设备号给占用if (ret<0){printk("chrdevbase init failed!\r\n");}printk("module_init!\r\n");return 0;
}
//驱动出口函数
static void __exit chrdevbase_exit(void)
{//注销字符设备驱动unregister_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME);printk("module_exit!\r\n");
};
/*模块的出口与入口*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);MODULE_AUTHOR("Chao");//作者是谁
MODULE_LICENSE("GPL");//开源协议

 chrdevbaseAPP.c

int atoi(const char *nptr);把输入的字符串转化为整型变量,因为我们的最后一个字符串1、2分别代表了对驱动设备的读写

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"/*
/dev/chardevbase:驱动最终表现就是/dev/xxx文件,对文件的读写、打开关闭
argc:应用程序参数
argv[]:具体的参数内容,是以字符串形式
./chrdevbaseAPP  <filename>  <1:2>
./chrdevbaseAPP /dev/chardevbase 1表示向驱动里面读数据
./chrdevbaseAPP /dev/chardevbase 2表示向驱动里面写数据
*/
int main(int argc,char*argv[]){int ret=0;int fd=0;char*filename;//输入的字符串char readbuf[100],writebuf[100];static char usrdata[]={"usr data!"};if (argc!=3)//如果输入的参数不足三个打印错误{printf("Error usage!\r\n");return -1;}filename=argv[1];//驱动文件就是/dev/chardevbase,也就是输入的第二个字符串fd=open(filename,O_RDWR);//打开驱动文件if(fd < 0){printf("Can't open file %s\r\n", filename);return -1;}
if (atoi(argv[2])==1)//判断是不是读操作,atoi把字符串转化为整型变量
{//从驱动中读取数据ret = read(fd, readbuf, 50);//返回值是实际读取到的字节数/*这里我们从驱动中读取50个字节,把内容写到readbuf中*/if(ret < 0){printf("read file %s failed!\r\n", filename);}else{/*  读取成功,打印出读取成功的数据 */printf("read data:%s\r\n",readbuf);}}if (atoi(argv[2])==2){memcpy(writebuf,usrdata,sizeof(usrdata));//把usrdata内容拷贝到writebuf中//向驱动中写数据ret = write(fd, writebuf, 50);//返回值是实际写入的字节数/*这里我们从buf中的50个字节,把内容写到驱动中*/if(ret < 0){printf("write file %s failed!\r\n", filename);}else{/*  写入成功,打印出写入成功的数据 */printf("write data:%s\r\n",writebuf);}}/* 关闭设备 */ret = close(fd);if(ret < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;
}

 三、测试运行

(1)编译chrdevbase.c生成chrdevbase.ko文件

(2)编译 chrdevbaseAPP.c使用交叉编译工具链编程成chrdevbaseAPP可执行文件

arm-linux-gnueabihf-gcc chrdevbaseAPP.c -o chrdevbaseAPP

 (3)ls查看编译后的驱动文件和应用程序文件

 (4)拷贝到/home/alientek/linux/nfs/rootfs/lib/modules/4.1.15文件下

这里存放着.ko文件

sudo cp chrdevbase.ko chrdevbaseAPP /home/alientek/linux/nfs/rootfs/lib/modules/4.1.15 -f

 我们这里是使用nfs挂载根文件系统,所以我们使用串口终端去查看该目录下有没有对应的文件

(5)加载驱动modprobe chrdevbase.ko

 (6)输入命令“ cat /proc/devices”可以注册的设备号、设备名字是否正确

我们在模块加载时,已经注册字符设备,我们使用这个命令来看看内核中是否有200这个设备号,设备名字是否正确

(7) 创建设备节点文件

在 Linux 内核中,当使用 register_chrdev() 函数,注册一个字符设备时,您实际上是在内核中注册了该设备的存在,并为其分配了一个主设备号;这个注册过程确保了内核知道如何处理对该设备的操作请求,但它并不在文件系统中创建任何可见的设备文件

设备文件(如 /dev/chrdevbase)是用户空间与内核中设备驱动程序进行交互的接口。这些文件不是普通的文件,而是特殊文件,通常称为设备特殊文件或设备节点。当您在用户空间中对这些文件执行读写操作时,内核会将这些操作转换为对相应设备驱动程序的调用。

 因此,即使已经在内核中注册了设备并为其分配了主设备号,仍然需要在文件系统中创建对应的设备文件,以便用户空间程序能够访问它。这就是为什么在注册字符设备之后,还需要用 mknod 命令来创建设备文件的原因。

创建设备文件节点

mknod /dev/chrdevbase c 200 0

其中“ mknod”是创建节点命令 ,“/dev/chrdevbase”是要创建的节点文件 c”表示这是个字符设备,“ 200”是设备的主设备号 0”是设备的次设备号。创建完成以后就会存在

 查看/dev/目录下有没有对应的驱动文件(chrdevbase 

ls /dev/chrdevbase -l

结果 

 (8)运行chrdevbaseAPP可执行文件

./chrdevbaseAPP /dev/chrdevbase 1 (向驱动中去读数据)

./chrdevbaseAPP /dev/chrdevbase 2 (向驱动中去写数据)

至此我们的字符设备驱动开发流程结束 

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

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

相关文章

快速入门,springboot知识点汇总

学习 springboot 应该像学习一门编程语言一样&#xff0c;首先要熟练掌握常用的知识&#xff0c;而对于不常用的内容可以简单了解一下。先对整个框架和语言有一个大致的轮廓&#xff0c;然后再逐步补充细节。 前序: Spring Boot 通过简化配置和提供开箱即用的特性&#xff0c…

第三期书生大模型实战营 第1关 Linux 基础知识

第三期书生大模型实战营 第1关 Linux 基础知识 第三期书生大模型实战营 第1关 Linux 基础知识InternStudio开发机创建SSH密钥配置通过本地客户端连接远程服务器通过本地VSCode连接远程服务器运行一个Python程序总结 第三期书生大模型实战营 第1关 Linux 基础知识 Hello大家好&a…

cesium 雷达扫描

cesium 雷达扫描 (下面附有源码) 实现思路 1、通过改变圆型材质来实现效果, 2、用了模运算和步进函数(step)来创建一个重复的圆形图案 3、当纹理坐标st落在垂直或水平的中心线上时,该代码将改变透明度和颜色,以突出显示这些线 示例代码 <!DOCTYPE html> <ht…

成为编程大佬!!——数据结构与算法(1)——算法复杂度!!

前言&#xff1a;解决同一个程序问题可以通过多个算法解决&#xff0c;那么要怎样判断一个算法的优劣呢&#xff1f;&#x1f914; 算法复杂度 算法复杂度是对某个程序运行时的时空效率的粗略估算&#xff0c;常用来判断一个算法的好坏。 我们通过两个维度来看算法复杂度——…

Maven在Windows中的配置方法

本文介绍在Windows电脑中&#xff0c;下载、配置Maven工具的详细方法。 Maven是一个广泛使用的项目管理工具&#xff0c;主要针对Java项目&#xff0c;但也可以用于其他类型的项目&#xff1b;其由Apache软件基金会维护&#xff0c;旨在简化和标准化项目构建过程&#xff0c;依…

数字经济时代,你有数商吗?

引言&#xff1a;随着科技的飞速发展&#xff0c;我们正步入一个全新的数字经济时代。在这个时代里&#xff0c;数据成为了新的石油&#xff0c;是推动经济增长和社会进步的关键要素。而在这个数据洪流中&#xff0c;一个新兴的概念——“数商”&#xff0c;正逐渐进入公众的视…

递归、搜索与回溯算法 2024.7.4-24.7.9

专题介绍&#xff1a; 一、递归 1、汉诺塔问题 class Solution {public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {int n A.size();move(n,A,B,C);// 将A柱上的n个盘子通过借助B盘子全部挪到C柱子上}void move(int m,List<Integ…

Python | Leetcode Python题解之第226题翻转二叉树

题目&#xff1a; 题解&#xff1a; class Solution:def invertTree(self, root: TreeNode) -> TreeNode:if not root:return rootleft self.invertTree(root.left)right self.invertTree(root.right)root.left, root.right right, leftreturn root

01_空中机器人

空中机器人&#xff08;Aerial Robotics&#xff09;最早由美国乔治亚理工大学的Robert Michelson提出&#xff0c;是指各种搭载了GPS、机载导航设备、视觉识别设备以及无线通信设备等&#xff0c;能够在一定的范围内实现无人飞行的旋翼无人飞行器、无人飞艇等。 空中机器人拓…

Zynq系列FPGA实现SDI视频编解码+图像缩放+多路视频拼接,基于GTX高速接口,提供8套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本博已有的FPGA图像缩放方案本方案的无缩放应用本方案在Xilinx--Kintex系列FPGA上的应用 3、详细设计方案设计原理框图SDI 输入设备Gv8601a 均衡器GTX 解串与串化SMPTE SD/HD/3G SDI IP核BT1120转RGB自研…

14-58 剑和诗人32 - 使用矢量数据库增强 LLM 应用程序

GPT-4、Bloom、LaMDA 等大型语言模型 (LLM) 在生成类似人类的文本方面表现出了令人印象深刻的能力。然而,它们在事实准确性和推理能力等方面仍然面临限制。这是因为,虽然它们的基础是从大量文本数据中提取统计模式,但它们缺乏结构化的知识源来为其输出提供依据。 最近,我们…

基于信号量的生产者消费者模型

文章目录 信号量认识概念基于线程分析信号量信号量操作 循环队列下的生产者消费者模型理论认识代码部分 信号量 认识概念 信号量本质: 计数器 它也叫做公共资源 为了线程之间,进程间通信------>多个执行流看到的同一份资源---->多个资源都会并发访问这个资源(此时易出现…

【Linux】进程(9):进程控制2(进程等待)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解Linux进程&#xff08;9&#xff09;进程控制2&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 为什么要进程等待二. 如何进行进程等待1.wait函数—…

使用linux的mail命令发送html格式的邮件

1、关闭本机的sendmail服务或者postfix服务 #执行下面的命令&#xff0c;各位大侠都对号入座吧 #sendmial service sendmail stop chkconfig sendmail off #postfix service postfix stop chkconfig postfix off#再狠一点就直接卸载吧.. yum remove sendmail yum remove postf…

欧拉部署nginx

1.下载nginx 下载地址&#xff1a;https://nginx.org/en/download.html 选择稳定版本 下的镜像文件进行下载 2.解压Nginx包 cd /root/nginx tar -zxvf nginx-1.26.0.tar.gz cd nginx-1.26.03.安装nginx相关依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl o…

如何在 CentOS 中配置 Linux 命名空间(ip netns)

引言 Linux 命名空间是一项强大的技术&#xff0c;允许在同一系统上创建多个独立的虚拟化实例&#xff0c;每个实例可以拥有自己的网络栈、路由表、IP 地址等网络资源&#xff0c;实现资源的隔离和管理。本文将深入探讨如何在 CentOS 中配置和使用 ip netns 命名空间&#xff0…

【面试题】正向代理和反向代理的区别?

正向代理&#xff08;Forward Proxy&#xff09;和反向代理&#xff08;Reverse Proxy&#xff09;是两种常见的代理服务器类型&#xff0c;它们在网络通信中扮演着不同的角色&#xff0c;具有不同的功能和应用场景。 一、正向代理 1. 定义与位置 正向代理是位于客户端和目标…

TextView 实现最后一行缩进指定距离

实现图上类似的效果。 指定最大行数为三行&#xff0c;最后一行缩进指定的距离。 如果行数小于三行&#xff0c;则不缩进。 同时文字两端对齐 代码里的 JustifyTextView &#xff08;两端对齐的 Textview &#xff09;详见 Android Textview 多行文本两端对齐_android tex…

Go语言入门之基础语法

Go语言入门之基础语法 1.简单语法概述 行分隔符&#xff1a; 一行代表一个语句结束&#xff0c;无需写分号。将多个语句写在一行可以用分号分隔&#xff0c;但是不推荐 注释&#xff1a; // 或者/* */ 标识符&#xff1a; 用来命名变量、类型等程序实体。 支持大小写字母、数字…

k8s核心操作_Deployment的扩缩容能力_Deployment自愈和故障转移能力---分布式云原生部署架构搭建022

然后我们上面说了k8s中的deployment的多副本能力 然后,我们再来看 k8s中的deployment的扩缩容能力 可以看到,对于扩容,要使用 kubectl scale 命令 对于缩容 要使用kubectl scale 命令都是使用这个命令对吧 来试试,可以看到上面命令 首先看看 kubectl get pod 可以看到有…