一、之前的驱动框架缺点
register_chrdev函数使用很浪费了很多次设备号。而且需要手动指定,如果冲突了,我们也不知道。还要手动使用mknod指令。
二、解决上述缺点
需要找到能自动向linux内核申请设备号的函数。使用alloc_chrdev_region函数。释放使用unregister_chrdev_region函数
//fs/char_dev.c
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
注册设备号
字符设备注册
cdev结构体表示字符设备,使用cdev_init函数来初始化cdev。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
void cdev_init(struct cdev *, const struct file_operations *);
自动创建设备结点
udev机制的引入在linux2.6以后。它具有对/dev的添加与删除的用户空间行为。之前使用静态的传统方法,现在udev提供热插拔管理,可以在加载驱动的时候自动创建设备结点。busybox提供了udev的简化版本mdev。
在构建根文件系统的时候,在/etc/init.d/rcS中添加了。
echo /sbin/mdev > /proc/sys/kernel/hotplug
三、新框架驱动代码
改变了创建设备号
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include <asm/uaccess.h>
#include<linux/io.h>
#include<linux/cdev.h>
/*
load module : mknod /dev/led c 200 0
*/
#define LED_NAME "led"
/*设备结构体*/
struct led_dev{
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
int name; /*设备名称*/
struct cdev cdev; /*cdev注册设备结构体*/
struct class *class; /*类结构体*/
struct device *device; /*设备*/
};
struct led_dev led;
/*
fileoperation的实现
*/
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
/*
注册与注销设备
*/
static int __init led_init(void){
int ret = 0;
printk("-----led_init-----\n");
Led_Init(); //LED初始化
/* 1.注册设备号 */
if( led.major ){ //给定主设备号
led.devid = MKDEV(led.major , 0);
ret = register_chrdev_region(led.devid , 1 , LED_NAME);
}else{
ret = alloc_chrdev_region(&led.devid, 0, 1, LED_NAME);
led.major = MAJOR(led.devid);
led.minor = MINOR(led.devid);
}
if(ret < 0){
printk("led chrdev region error!\r\n");
return -1;
}
printk("led major = %d , minor = %d" ,led.major , led.minor );
/* 2.字符设备注册 */
led.cdev.owner = THIS_MODULE;
cdev_init(&led.cdev , &led_fops);
ret = cdev_add(&led.cdev, led.devid , 1);
/* 3.自动创建设备结点 */
led.class = class_create(THIS_MODULE , LED_NAME);
if(IS_ERR(led.class))
return PTR_ERR(led.class);
led.device = device_create(led.class , NULL , led.devid , NULL , LED_NAME);
if(IS_ERR(led.device))
return PTR_ERR(led.device);
return 0;
}
static void __exit led_exit(void){
printk("-----Led_exit-----\n");
/*删除字符设备*/
cdev_del(&led.cdev);
/*注销设备号*/
unregister_chrdev_region(led.devid, 1);
/*删除设备*/
device_destroy(led.class , led.devid);
/*删除类*/
class_destroy(led.class);
}
四、测试驱动设备
1.加载驱动程序
2.控制台手动创建设备结点
mknod /dev/led c 248 0 #使用动态申请设备号,还是得先加载驱动程序得到设备号
3.开启应用程序。
./led_app /dev/led 1 //1是写写入数据,来开关Led灯
五、文件私有数据
在open接口中的file结构体中的private_data成员指向我们的设备结构体,就可以将其设置为设备的私有数据,在read,write中可以直接使用private_data变量就可以读取私有数据。
static int led_open(struct inode *inode, struct file *filp){
printk("------led_open-------\r\n");
filp->private_data = &led;
return 0;
}
int led_release(struct inode *inode, struct file *filp){
printk("------led_release--------\r\n");
struct led_dev *led = (struct led_dev *dev)filp->private_data;
return 0;
}
\