《嵌入式Linux驱动开发教程》--字符设备驱动

字符设备驱动



Linux下根据驱动程序的模型框架分为了三类
(1)字符设备驱动:对数据处理按照字节流的形式进行,可以支持随机访问,也可以不支持,典型设备:串口、键盘、帧缓存设备(显卡)。
(2)块设备驱动:对数据的处理按照若干块进行,将之前读取的数据放在页高速缓存的内存中,都支持随机访问,典型设备:硬盘。
(3)网络设备驱动:进行网络数据的收发。


3.1字符设备驱动基础

1.设备文件位于/dev目录下。
  b:代表块设备 c:代表字符设备

2.主设备号和次设备号是设备在内的中的标志,是内核区分不同设备的唯一信息。
  主设备号和次设备号统称为设备号
  主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。
  主设备号用来表示一个特定的驱动程序,次设备号用来表示使用该驱动程序的各设备。
在这里插入图片描述

3.mknod创建一个设备文件:将文件名,类型,主次设备号等信息保存在硬盘上。
   Linux中一个节点代表一个文件,创建文件的根本是分配一个新的节点
   命令原型:mknod 设备结点的路径,类型,主次设备号
   命令实例:mknod /dev/vser0 c 256 0

4.如何打开一个设备在这里插入图片描述
图片说明:
第一行:进程用task_struct结构体表示,task_struct中的files成员指向files_struct结构体,
    files_struct结构体中的fd_array指针数组保存打开文件的信息,
    fd_array中每一个元素是file类型,每一个file中有一个f_op成员,
    f_op成员代表该file的操作方法并与其匹配的驱动中的操作方法关联。
    
第二、三行表示file的操作并与其匹配的驱动中的操作方法关联的步骤(驱动和内核的关联)。

第二行:从右往左理解,cdev是的驱动创建的特定设备,其保存在cdev_map散列表中。
    从左往右就是从cdev_map散列表中取出probe结构的data,该成员指向cdev结构地址。
    
第三行:根据内存中inode结构,通过文件类型调用指定的该类型设备的操作,再通过设备号调用     指定设备的操作。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结:
 打开操作返回了一个设备描述符fd,其他系统调用时,以这个文件描述符为参数传递给内核,内核在fd_array索引,找到file结构,找到相应的操作方法。
 1.fd是指向新创建的file结构在fd_array数组的下表,返回给应用程序的打开文件的文件描述符。
 2. 设备驱动的框架是设备号、cdev、操作方法集合实现的
 3.内核第一次打开文件后创建dentry目录,用以保存了文件名和对应inode信息,并将dentry保存在高速缓存中
 所以第一次打开要在磁盘中查找,之后可以立即获取文件对应的inode.


3.2字符设备驱动框架

1.框架主要功能
在这里插入图片描述

2.相关函数

//静态的申请和注册设备号 ,可以一次注册多个
int register_chrdev_region(dev_t from, unsigned count, const char * name); 
		dev_t from: 起始的设备号(就是自己指定的那个)
		unsigned count:指定连续注册个数
		const char * name: 设备名称
		返回值:0代表成功,返回负数多为要注册的设备号被其他驱动注册
//动态的申请和注册设备号 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
	    dev_t * dev :alloc_chrdev_region函数向内核申请下来的设备号
	    unsigned  baseminor :次设备号的起始(因为主设备代表驱动,所以次设备号增1,就能代表不同设备)
	    unsigned  count: 申请次设备号的个数
	    char *name :执行 cat /proc/devices显示的名称
	    返回值:0代表成功,-1表示失败
//初始化字符设备的部分成员
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
	   struct cdev *cdev:要初始化的字符设备
	   const struct file_operations *fops:设备操作方法集合的结构地址
//设备添加到cdev_map散列表中(链表probe的data指向cdev结构地址)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
	   struct cdev *p:要添加的字符设备
	   dev_t dev:设备所使用的设备号
	   unsigned count:要添加的设备数量
	   返回值:0代表成功,如果失败,则返回- ENOMEM, ENOMEM的被定义为12
//设备删除自cdev_map散列表中
void  cdev_del(struct cdev *cdev *p)
	   struct cdev *p:要删除的字符设备

驱动框架

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#define VSER_MAJOR	256//主设备号
#define VSER_MINOR	0  //次设备号
#define VSER_DEV_CNT	1
#define VSER_DEV_NAME	"vser"

//一个字符设备
static struct cdev vsdev;  
//设备操作
static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
};
//模块初始化
static int __init vser_init(void)
{
	int ret;
	dev_t dev;  	//dev_t 32位的整数 前12主设备,后20次设备号														
	// 1.构造设备号
	dev = MKDEV(VSER_MAJOR, VSER_MINOR);	

	// 2.将构造的设备号,注册到内核							
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);    
	if (ret)
		goto reg_err;

	// 3.初始化字符设备 设备与操作绑定 
	cdev_init(&vsdev, &vser_ops);                                                         
	vsdev.owner = THIS_MODULE;

 	// 4.将构建的设备添加到散列表中
	ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);							
	if (ret)
		goto add_err;

	return 0;

	// 如果添加对象失败的话注销掉设备号
add_err:
	unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
	return ret;
}
//模块卸载时
static void __exit vser_exit(void)
{
	
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
	// 删除掉设备对象
	cdev_del(&vsdev);
	// 注销掉设备号
	unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

3.3虚拟串口设备以及驱动

1.串口设备,实现一个FIFO(struct kfifo)
// 初始化一个虚拟串口缓冲区
	DEFINE_KFIFO(fifo, type, size);	
		fifo:FIFO变量的名字
		type:FIFO成员的类型
		size:FIFO中有多少个type类型的元素,元素个数为2的幂
	DEFINE_KFIFO(vsfifo, char, 32);//定义一个32个char的FIFO叫vsfifo
	//将用户空间的数据放入FIFO
	kfifo_from_user(fifo, from, len, copied);
		fifo:FIFO名称
		from: 用户空间的数据
		len: 指定复制的元素个数
		copied:返回的实际复制入的元素个数
	//将FIFO的数据复制到用户空间
	kfifo_to_user(fifo, to, len, copied);
		fifo:FIFO名称
		to: 用户空间
		len:  指定复制的元素个数
		copied:返回的实际复制的元素个数
2.串口设备驱动(对FIFO的读写操作)

//struct inode *inode:要打开文件的inode,
//struct file *filp :打开文件后由内核构造初始化后的file结构
在这里插入图片描述

//struct file *filp :打开文件后由内核构造初始化后的file结构
//char __user *buf :用户空间的内存起始地址
//size_t count :用户要读写的字节数
//loff_t *pos :随机读取用
在这里插入图片描述


3.4一个驱动支持多个设备

open接口有一个inode形参,inode包括设备的设备号以及cdev对象的地址,可以用以区分设备。

1.一个cdev对象,cdev_add时指定cdev可以管理多个设备\程序源码 \chrdev\ex4
  1.1初始化一个cdev
  1.2直接在散列表中加入两个cdev(vsdev相同,dev不同)
    ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
  1.3filp->private_data 直接指向FOFO地址

define VSER_MAJOR	256
#define VSER_MINOR	0
//说明两个设备
#define VSER_DEV_CNT	2
#define VSER_DEV_NAME	"vser"

static struct cdev vsdev;
//定义两个FIFO
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);


//MINOR用以获得设备的次设备号
//根据inode中的设备号,指定不同的FIFO
//filp->private_data 是void *类型的指针,内核不会使用
static int vser_open(struct inode *inode, struct file *filp)
{
	switch (MINOR(inode->i_rdev)) {
	default:
	case 0:
		filp->private_data = &vsfifo0;
		break;
	case 1:
		filp->private_data = &vsfifo1;
		break;
	}
	return 0;
}

//对filp->private_data所指向的FIFO进行操作
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
	unsigned int copied = 0;
	struct kfifo *vsfifo = filp->private_data;

	kfifo_to_user(vsfifo, buf, count, &copied);

	return copied;
}
static int __init vser_init(void)
{
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
	//向内核注册两个设备号(256 0;256 1)
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if (ret)
		goto reg_err;
	//初始化一个cdev
	cdev_init(&vsdev, &vser_ops);
	vsdev.owner = THIS_MODULE;
	//将两个cdev设备加入到cdev_map散列表
	ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
	if (ret)
		goto add_err;

	return 0;

add_err:
	unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
	return ret;
}

2.每一个设备分配一份cdev对象,多次cdev_add \程序源码\chrdev\ex5
  2.1初始化两个cdev
  2.2循环在散列表中加入两个cdev(vsdev不同,dev也不同)
  ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
  2.3filp->private_data 指向vser_dev结构体的地址,FOFO是vser_dev结构体中成员

 //宏:根据数据结构成员地址反向得到数据结构的起始地址
 container_of(ptr, type, member) 
	ptr:表示结构体中member的地址
	type:表示结构体类型
	member:表示结构体中的成员
#define VSER_MAJOR	256
#define VSER_MINOR	0
#define VSER_DEV_CNT	2
#define VSER_DEV_NAME	"vser"

static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);

struct vser_dev {
	struct kfifo *fifo;
	struct cdev cdev;
};

static struct vser_dev vsdev[2];

//MINOR用以获得设备的次设备号
//根据inode中的设备号,指定不同的vser_dev结构
//filp->private_data 是void *类型的指针,内核不会使用
//container_of:根据结构体变量中的一个成员地址,反推,该结构体变量的起始地址
static int vser_open(struct inode *inode, struct file *filp)
{
	filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
	return 0;
}


//对filp->private_data所指向的vser_dev结构中的FIFO变量进行操作
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
	unsigned int copied = 0;
	struct vser_dev *dev = filp->private_data;

	kfifo_to_user(dev->fifo, buf, count, &copied);

	return copied;
}

static int __init vser_init(void)
{
	int i;
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
	//向内核注册两个设备号(256 0;256 1)
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if (ret)
		goto reg_err;
	//循环向vsdev[i]各个成员赋值
	for (i = 0; i < VSER_DEV_CNT; i++) {
		cdev_init(&vsdev[i].cdev, &vser_ops);
		vsdev[i].cdev.owner = THIS_MODULE;
		vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;

		ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
		if (ret)
			goto add_err;
	}

	return 0;

add_err:
	for (--i; i > 0; --i)
		cdev_del(&vsdev[i].cdev);
	unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
	return ret;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值