Linux驱动学习-新字符设备驱动
之前学习的是使用register_chrdev函数注册字符设备,使用unregister_dev注销字符设备,驱动模块加载成功以后还需要手动使用 mknod 命令创建设备节点。
在新的字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动API函数。
1、新字符设备驱动原理
①分配和释放设备号
使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会
带来两个问题:
①、需要我们事先确定好哪些主设备号没有使用。
②、会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为
200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。这样太浪
费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设备号。
新字符设备驱动 会在需要使用设备号的时候像Linux内核申请,这样就不会出现上述的问题
如果没有申请主设备号,就使用alloc_chrdev_region申请设备号,*dev返回设备号信息
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)
参数 from 是要申请的起始设备号,也就是给定的设备号;参数 count 是要申请的数量,一
般都是一个;参数 name 是设备名字。
通用的代码如下:
int major; /* main dev*/
int minor; /* sub dev */
dev_t devid /* dev */
if(major)
{
devid = MKDEV(major, 0); /* sub dev = 0 */
register_chrdev_region(devid, 1, "test");
}
else
{
alloc_chrdev_region(&devid, 0, 1, "test");
major = MAJOR(devid); /* get main dev */
minor = MINOR(devid); /* get sub dev */
}
上面三个全局变量可以用结构体来实现,代码可读性更强。
②新字符设备的注册方法
在 Linux 中使用 cdev 结构体表示一个字符设备
->字符设备结构
struct cdev
{
struct module *owner;
const struct file_operations *ops;
dev_t dev;
}
->cdev_init函数
定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化,cdev_init 函数原型如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
~~把 file_operations 赋给 cdev.xxx
->code_add函数
cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。
cdev_add 函数原型如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
testcdev.owner = THIS_MODULE;
cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */
cdev_add(&testcdev, devid, 1); /* 添加字符设备 */
Linux 内核中大量的字符设备驱动都是采用这种方法向 Linux 内核添加字符设备。
就它们一起实现的就是函数 register_chrdev 的功能。
->cdev_del 函数
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备,cdev_del
函数原型如下:
void cdev_del(struct cdev *p)
cdev_del 和 unregister_chrdev_region 这两个函数合起来的功能相当于 unregister_chrdev 函
数。
2、自动创建设备节点
1>mdev 机制
udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除。
busybox 会创建一个 udev 的简化版本—mdev,在嵌入式 Linux 中我们使用mdev 来实现设备节点文件的自动创建与删除,Linux 系统中的热插拔事件也由 mdev 管理,在/etc/init.d/rcS 文件中如下语句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
2>创建和删除类
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。
class_create
struct class *class_create (struct module *owner, const char *name)
class_destroy
void class_destroy(struct class *cls)
3>创建设备和删除设备
创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设备。
device_create
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, …)
device_destroy
void device_destroy(struct class *class, dev_t devt)
4>设置文件私有数据
typedef struct
{
dev_t devid; /* */
struct cdev cdev; /* cdev(加载和卸载驱动) */
struct class *class; /* 类(设备节点) */
struct device *device; /* 设备(设备节点) */
int major; /* 主设备号 */
int minor; /* 次设备号 */
}newchardev_dev;
编写驱动 open 函数的时候将设备结构体作为私有数据添加到设备文件中,如下所示:
static int newchrdev_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrdev; /* 设置私有数据 */
return 0;
}
在 open 函数里面设置好私有数据以后,在 write、read、close 等函数中直接读取 private_data即可得到设备结构体。