字符设备和杂项设备的区别
杂项设备的主设备号10是固定的,分配的是次设备号;在字符设备中要自己申请设备号并注册到系统中。
字符设备
字符设备按照复杂程度也可以分为:简单字符设备+分层字符设备(fbmem驱动和v4l2驱动也属于字符设备驱动,并且是分层驱动)
简单字符设备驱动编译一般步骤:
- 申请设备号
- 申请cdev内存
- 初始化cdev
- 向内核注册一个字符类设备
用到的相关函数:
#include <linux/fs.h>
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
#include <linux/cdev.h>
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);
这样设备驱动就完成了,但是在用户空间还不能使用。还需要
用mknod:mknod DEVNAME {b | c} MAJOR MINOR //b为块设备,c为字符设备
比如:mknod /dev/key_name c 236 1
在/dev目录中创建字符设备或块设备文件节点,/dev为linux中将设备抽象虚拟为文件。
在linux系统中,设备文件是特殊的文件类型,主要作用是沟通用户空间程序和内核空间驱动程序。如果驱动程序只是作为内核程序或者为其它内核模块服务,就没必要生成设备文件。
也可以在程序中自动创建设备:
- 创建一个类
- 在类中根据需要创建设备
class_create();
device_create();
字符设备分为静态申请和动态申请,静态申请就是主设备号是程序员手动分配了,动态申请是系统给分配.
int MAJOR(dev_t dev) //从设备号中获取主设备号
int MINOR(dev_t dev) //从设备号中获取次设备号
dev_t MKDEV(unsigned int major, unsigned int minor); //用主设备号和次设备号生成设备号
疑问:为什么在注册多个字符设备时,只使用了一次alloc_chrdev_region()函数?
注意:alloc_chrdev_region()函数可以分配1个主设备号+多个次设备号
字符设备不需要进行设备注册+驱动注册的方式。
新旧字符设备驱动注册
旧设备驱动的缺陷:需要事先知道哪些主设备号没被使用,以便确定需要使用的设备号;并且会依次占用对应的此设备号。
旧版字符设备注册
实际上对申请设备号、申请设备内存、初始化cdev、系统注册步骤的集合。
注册和注销函数原型如下所示:
#include <linux/fs.h>
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
新设备号申请
如果没有指定设备号的话就使用如下函数来申请设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
ps:优先选择alloc_chrdev_region 而不是 register_chrdev_region。
新设备释放
函数申请的设备号,统一使用如下释放函数:
void unregister_chrdev_region(dev_t from, unsigned count)
设备私有数据
在打开设备时,可以设置私有数据,在write、read、close等函数中直接读取private_data即可得到设备结构体。
struct test_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
static int test_open(struct inode *inode, struct file *filp)
{
filp->private_data = &testdev; /* 设置私有数据 */
return 0;
}
说明:
新版在2.4版本后加入,旧版中在内核中最多能申请256个字符主设备,每个主设备可以有256个次设备,
但是主设备的所有次设备都公用一个file_operation并且在使用register_chrdev时,会一次申请完0-256个次设备号。
所以在内核中最多有256种字符设备。
新版中在一个主设备可以分段申请:比如0-1,它们公用一个file_operation;
2-3,它们公用一个file_operation.所以,一个主设备就可以包含256种字符设备,内核中最多有256*256中字符设备。
创建设备节点:
头文件:include/linux/device.h”
1.先创建一个类
class_create(owner, name);
2.在类中创建设备节点
extern struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...);
创建并注册类:
struct class *class_create(struct module *owner, const char *name); //创建类并注册到内核
void class_destroy(struct class *cls);
创建设备并注册:
struct device *device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...)
void device_destroy(struct class *cls, dev_t devt);
cat /proc/devices //显示设备及主设备号
ls /sys/class //显示设备类别
ls /sys/class/xxx //显示设备类别中的xxx类设备
ls /dev //显示设备节点
register_chrdev_region中的设备名字可以在/proc/devices中看到
class_create中的类名字在/sys/class中看到
device_create中的名字在/sys/class/xxx/中看到,也会在/dev中生成设备节点
加载驱动:insmod xxx.ko
显示驱动:lsmod
卸载驱动:rmmod xxx //注意没有.ko
设备类
Linux 中的class 是设备类,它是一个抽象的概念,没有对应的实体。它是提供给用户接口相似的一类设备的集合。常见的有输入子系统input、usb、串口tty、块设备block 等。
以4412 的串口为例,它有四个串口,不可能为每一个串口都重复申请设备以及设备节点,因为它们有类似的地方,而且很多代码都是重复的地方,所以引入了一个抽象的类,将其打包为ttySACX,在实际调用串口的时候,只需要修改X 值,就可以调用不同的串口。
对于本实验中的设备,它有两个子设备,将对应两个设备节点,所以需要用到class 类这样一个概念。