字符设备驱动
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;
}