驱动开发之字符设备开发

1.概念

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节 流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

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

2.对驱动的操作

在应用层,对文件操作接口无非就是open, read,write, close,ioctl等接口,这些接口都是c库接口。应用层到glibc接口之后,通过系统调用,陷入进入内核,找到相应的驱动或者inode节点的操作函数,完成对硬件的操作,如下图所示:

3.字符设备驱动开发接口

从应用层角度看,需要open,read,write,close,ioctl等接口,那内核层也应该实现相应的接口,这就对应到了file_operations 的结构体了此结构体就是 Linux 内核驱动操作函数集合。如下:

示例代码 40.1.1 file_operations 结构体
1588 struct file_operations {
1589 struct module *owner;
1590 loff_t (*llseek) (struct file *, loff_t, int);
1591 ssize_t (*read) (struct file *, char __user *, size_t, loff_t 
*);
1592 ssize_t (*write) (struct file *, const char __user *, size_t,
loff_t *);
1593 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1594 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1595 int (*iterate) (struct file *, struct dir_context *);
1596 unsigned int (*poll) (struct file *, struct poll_table_struct 
*);
1597 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned
long);
1598 long (*compat_ioctl) (struct file *, unsigned int, unsigned
long);
1599 int (*mmap) (struct file *, struct vm_area_struct *);
1600 int (*mremap)(struct file *, struct vm_area_struct *);
1601 int (*open) (struct inode *, struct file *);
1602 int (*flush) (struct file *, fl_owner_t id);
1603 int (*release) (struct inode *, struct file *);
1604 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1605 int (*aio_fsync) (struct kiocb *, int datasync);
1606 int (*fasync) (int, struct file *, int);
1607 int (*lock) (struct file *, int, struct file_lock *);
1608 ssize_t (*sendpage) (struct file *, struct page *, int, size_t,
loff_t *, int);
1609 unsigned long (*get_unmapped_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
1610 int (*check_flags)(int);
1611 int (*flock) (struct file *, int, struct file_lock *);
1612 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,
loff_t *, size_t, unsigned int);
1613 ssize_t (*splice_read)(struct file *, loff_t *, struct
pipe_inode_info *, size_t, unsigned int);
1614 int (*setlease)(struct file *, long, struct file_lock **, void
**);
1615 long (*fallocate)(struct file *file, int mode, loff_t offset,
1616 loff_t len);
1617 void (*show_fdinfo)(struct seq_file *m, struct file *f);
1618 #ifndef CONFIG_MMU
1619 unsigned (*mmap_capabilities)(struct file *);
1620 #endif
1621 };

这些函数我们不需要都实现,应用层需要使用哪些,驱动层再实现即可 。如果驱动层不实现,应用层直接使用接口会返回报错,这个时候我们使用perror可以查看错误。

4.驱动层实现字符设备的API说明

4.1.注册字符设备函数

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

参数说明:

major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两 部分,关于设备号后面会详细讲解。

name:设备名字

fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量。

注意:该函数一般是在驱动模块的入口函数 xxx_init 中进行的,也可能在platform总线driver的probe函数 

4.2.注销字符设备函数

static inline void unregister_chrdev(unsigned int major, const char *name)

参数说明:

major:要注销的设备对应的主设备号。

name:要注销的设备对应的设备名。 

 注意:该函数一般是在驱动模块 的出口函数 xxx_exit 中进行,也可能在platform总线driver的remove函数 

 通过这两个函数就可以完成一个简单的字符设备驱动的注册了,接下来只需要实现结构体 file_operations里面的某一些函数就可以了·,是不是感觉很简单。

4.3.实现结构体 file_operations的部分函数

这个结构体的定义在 include/linux/fs.h里面,每一个函数类型都有定义好,把需要实现的函数拷贝到自己的源文件里面进行实现即可。或者我们可以到linux其他字符设备里面看看别人怎么实现的,包含哪些头文件,我们直接抄作业。

如:

/* 打开设备 */
static int chrtest_open(struct inode *inode, struct file *filp)
{
/* 用户实现具体功能 */
    return 0;
}

/* 从设备读取 */
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
/* 用户实现具体功能 */
    return 0;
}

/* 向设备写数据 */
static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    /* 用户实现具体功能 */
    return 0;
}


/* 关闭/释放设备 */
static int chrtest_release(struct inode *inode, struct file *filp)
{
    /* 用户实现具体功能 */
    return 0;
}

static struct file_operations test_fops = {
.owner = THIS_MODULE, 
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
};

4.4.设备号

 现在万事俱备,只欠设备号了。

Linux 中每个设备都有一个设备号,设备号由主设备号次设备号两部分 组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备,通过设备号可以定位到相应的设备,可以说这是一种标识。

Linux 使用 一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,定义如下:

typedef __u32 __kernel_dev_t;

typedef __kernel_dev_t dev_t;

这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号,低 20 位为次设备号。

看到位操作不要慌,内核大佬都给我们考虑好了,include/linux/kdev_t.h 封装了很多宏给开发者使用。

#define MINORBITS 20  /** 表示次设备号位数,一共是 20 位 */
#define MINORMASK ((1U << MINORBITS) - 1)   /**  表示次设备号掩码 */

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))   /** 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可 */
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))   /** 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可 */
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))   /** 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号 */

4.1.设备号的分配

知道了设备号组成规则,那怎么分配呢???

目前有两种分配方式,一种是静态,一种是动态的。

4.1.1.静态分配设备号

有一些常用的设备号已经被 Linux 内核开发者给分配掉 了,具体分配的内容可以查看文档 Documentation/devices.txt。可以使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号。

注意:可能cat /proc/devices查看某一个设备号没有被使用,但是在Documentation/devices.txt文档里面占用了,我们就尽量不要使用了,防止冲突,当然只是学习就无所谓了。

4.1.2.动态分配设备号

静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用 的。而且静态分配设备号很容易带来冲突问题,Linux 社区推荐使用动态分配设备号,在注册字 符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。 卸载驱动的时候释放掉这个设备号即可,

4.1.2.1设备号的申请函数

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)。

参数如:

dev:保存申请到的设备号。

baseminor:次设备号起始地址,

alloc_chrdev_region 可以申请一段连续的多个设备号,这 些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递 增。一般 baseminor 为 0,也就是说次设备号从 0 开始。

count:要申请的设备号数量。

name:设备名字。

4.1.2.2.设备号释放函数

注销字符设备之后要释放掉设备号,设备号释放函数如下:

void unregister_chrdev_region(dev_t from, unsigned count) 

参数:

from:要释放的设备号。

count:表示从 from 开始,要释放的设备号数量。

4.1.2.3.注意

register_chrdev第一个参数给0也是动态申请的,但是这样就确定不了设备号,无法注销设备号,导致设备泄露,可能有其他方法,目前还没有发现,所以动态申请还是使用alloc_chrdev_region好一点,后面会单独出一篇博客将该接口使用demo.

 ok,目前一切准备工作都准备完成了,开始进行测试。

5.测试

5.1.驱动代码:

todo: 暂时贴到博客,后续会整理放到gitee上面。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>


#define CHRDEVBASE_MAJOR	200		/* 主设备号 */
#define CHRDEVBASE_NAME		"chrdevbase" 	/* 设备名     */


/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	printk("chrdevbase open!\r\n");
	return 0;
}

/*
 * @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)
{
	
	printk("chrdevbase read!\r\n");
	return 0;
}

/*
 * @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)
{
	printk("chrdevbase write!\r\n");
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	printk("chrdevbase release!\r\n");
	return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("seven");


5.2.应用层测试用例

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

static char usrdata[] = {"usr data!"};

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];
	if(argc != 2){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开驱动文件 */
	fd  = open(filename, O_RDWR);
	if(fd < 0){
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	retvalue = write(fd, writebuf, 50);
	if(retvalue < 0){
		perror("write error");
		printf("write file %s failed!\r\n", filename);
	}

	/* 关闭设备 */
	retvalue = close(fd);
	if(retvalue < 0){
		printf("Can't close file %s\r\n", filename);
		return -1;
	}

	return 0;
}



5.3.  创建设备节点

mknod /dev/chrdevbase c 200 0 创建设备节点。

其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个 字符设备,“200”是设备的主设备号,“0”是设备的次设备号。创建完成以后就会存在 /dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看。有没有感觉这样很麻烦,每换一个设备号,又得重新创建设备节点,能不能自动创建呢??当然可以了,内核udev提供了自动创建设备节点的功能。由于篇幅问题,将在下一篇文章里面进行解释怎么使用udev的接口自动创建设备。

5.4.测试结果:

 

 由于篇幅太长,测试用例就没写那么多,测试一些奇怪的使用方法,将在后面慢慢补齐。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法在计算中的作用

前言 算法在基因工程&#xff0c;互联网&#xff0c;电子商务和制造业中都有广泛的应用。最近的智能驾驶和人工智能也处处有着算法的影子。 数据结构 数据结构是数据元素存在的一种形式&#xff0c;有线性和非线性的区别。常见的线性的有链表&#xff0c;数组&#xff0c;栈和…

Modal.method() 不显示头部的问题

ant-design中的Modal组件有两种用法&#xff1a; 第一种是用标签&#xff1a;<a-modal></a-modal> 第二种是用Api&#xff1a;Modal.info、Modal.warning、Modal.confirm...... 一开始项目中这两种用法是混用的&#xff0c;后面UI改造&#xff0c;需要统一样式&…

C++第十七弹---string使用(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、标准库中的string类 1.1、string类的常用接口说明 1.1.1、string类对象的修改操作 1.1.2、string类对象非成员函数重载 总结 1、标准库中的…

修改元组元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 场景模拟&#xff1a;伊米咖啡馆&#xff0c;由于麝香猫咖啡需求量较大&#xff0c;库存不足&#xff0c;店长想把它换成拿铁咖啡。 实例08 将麝香猫…

[笔试训练](三十三)097:跳台台阶扩展问题098:包含不超过两种字符的最长子串099:字符串的排列

目录 097:跳台台阶扩展问题 098:包含不超过两种字符的最长子串 099:字符串的排列 097:跳台台阶扩展问题 题目链接:跳台阶扩展问题_牛客题霸_牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 规律题: 1.跳上n级台阶的跳法等于前面1~(n-1)级台阶跳法的总和1。 2.跳…

WWW 2024最佳论文|大型语言模型的机制设计

【摘要】我们研究拍卖机制以支持人工智能生成内容的新兴格式。我们特别研究如何以激励兼容的方式聚合多个法学硕士。在这个问题中&#xff0c;每个代理对随机生成的内容的偏好被描述/编码为 LLM。一个关键动机是为人工智能生成的广告创意设计一种拍卖格式&#xff0c;以结合不同…

GEC210编译环境搭建

一、下载编译工具链 下载&#xff1a;点击跳转 二、解压到 /usr/local/arm 目录 sudo mv gec210.zip /usr/local/arm cd /usr/local/arm sudo unzip gec210.zip 三、添加到环境变量 PATH/usr/local/arm/arm-cortex_a8-linux-gnueabi-4.7.3/bin:$PATH 四、测试验证 在终端…

网络原理-以太网协议和DNS协议

一、以太网协议 以太网协议会涉及到数据链路层和物理层。 如图&#xff1a; 这里面的目的地址和源地址指的并不是IP地址,而是MAC地址(物理地址)。长度为6个字节。即最多能表示2^48 个地址,也是非常大的,足够给全球每个设备都分配一个地址,因此在网卡出厂的时候都会带有一个唯…

Mysql之主从同步

1.BinLog同步机制 Mysql要去保证高可用&#xff0c;或者去分担请求压力&#xff0c;一般会去主从部署&#xff0c;读写分离。写库只负责写&#xff0c;而读库更多的去承担读的请求&#xff0c;从库不写数据&#xff0c;数据从主库同步&#xff0c;那么到底是怎么同步的呢&…

ant design pro 6.0列表渲实践demo

ant design pro 用户列表渲实践 用户页面&#xff1a; src\pages\Admin\User\index.tsx import { PlusOutlined } from ant-design/icons; import type { ActionType, ProColumns, ProDescriptionsItemProps } from ant-design/pro-components; import {PageContainer,ProDe…

Pycharm2024搭建QT6开发环境

创建pyqt6虚拟环境 首先&#xff0c;创建一个qt6的虚拟环境&#xff1a; conda create --name pyqt6 python3.11.7激活环境&#xff1a; conda activate pyqt6安装pyqt6 安装pyqt6&#xff1a; pip install pyqt6创建代码目录 创建目录&#xff1a; 使用pycharm打开这个…

springboot + Vue前后端项目(第十一记)

项目实战第十一记 1.写在前面2. 文件上传和下载后端2.1 数据库编写2.2 工具类CodeGenerator生成代码2.2.1 FileController2.2.2 application.yml2.2.3 拦截器InterceptorConfig 放行 3 文件上传和下载前端3.1 File.vue页面编写3.2 路由配置3.3 Aside.vue 最终效果图总结写在最后…

分享活动规划

前两天去参加菁英学院的一些辅导&#xff0c;是关于苏州久富农业机械的发展&#xff0c;看了他们企业的故事&#xff0c;我觉得我们农机很有前景和发展空间&#xff0c;我希望重新经过一次分享活动来分享我的感触&#xff0c;希望能够再次把我学到的内容传输到其他班的同学们 请…

主干网络篇 | YOLOv8更换主干网络之MobileNeXt | 新一代移动端模型MobileNeXt来了!

前言:Hello大家好,我是小哥谈。MobileNeXt是由微软研究院提出的一种高效的卷积神经网络结构,它在保持模型轻量级的同时,能够获得较高的性能。MobileNeXt采用了一种称为Inverted Residuals with Linear Bottlenecks(IRL)的结构,通过深度可分离卷积和快捷连接来减少模型的…

洗地机十大品牌排名:2024十大值得入手的洗地机盘点

随着生活水平的提高&#xff0c;智能清洁家电已经成为日常生活中的必需品。洗地机之所以在家庭清洁中大受欢迎&#xff0c;主要是因为它的多功能特性。传统的清洁方式通常需要扫帚、拖把和吸尘器分别进行操作&#xff0c;而洗地机将这些功能集成在一个设备中&#xff0c;使清洁…

谷歌Google广告投放优势和注意事项!

谷歌Google作为全球最大的搜索引擎&#xff0c;谷歌不仅拥有庞大的用户基础&#xff0c;还提供了高度精准的广告投放平台&#xff0c;让广告主能够高效触达目标受众&#xff0c;实现品牌曝光、流量增长乃至销售转化的多重目标&#xff0c;云衔科技以专业服务助力您谷歌Google广…

【mysql】in和exists的区别,not in、not exists、left join的相互转换

【mysql】in和exists的区别&#xff0c;not in、not exists、left join的相互转换 【一】in介绍【1】in中数据量的限制【2】null值不参与in或not in&#xff0c;也就是说in and not in 并不是全量值&#xff0c;排除了null值【3】in的执行逻辑 【二】exists介绍【1】exists no…

-bash: locate: 未找到命令(解决办法)

-bash: locate: 未找到命令的解决办法 一、解决办法二、什么是locate三 、locate命令的具体用法 一、解决办法 CentOS7默认没有安装locate命令&#xff0c;安装方式如下&#xff1a; 执行以下命令进行安装&#xff1a; yum install mlocate用 updatedb 指令创建 或更新locate …

Value-Based Reinforcement Learning(2)

Temporal Difference &#xff08;TD&#xff09; Learning 上节已经提到了如果我们有DQN&#xff0c;那么agent就知道每一步动作如何做了&#xff0c;那么DQN如何训练那&#xff1f;这里面使用TD算法。 简略分析&#xff1a; 是的估计 是的估计 所以&#xff1a; Deep Re…

【论文阅读】Prompt Fuzzing for Fuzz Driver Generation

文章目录 摘要一、介绍二、设计2.1、总览2.2、指导程序生成2.3、错误程序净化2.3.1、执行过程净化2.3.2、模糊净化2.3.3、覆盖净化 2.4、覆盖引导的突变2.4.1、功率调度2.4.2、变异策略 2.5、约束Fuzzer融合2.5.1、论据约束推理2.5.1、模糊驱动融合 三、评估3.1、与Hopper和OSS…