旧字符设备驱动程序的框架:
1、确定主设备号major;
2、构造file_operations
3、注册register_chrdev;
4、创建类和设备结点
这个框架有很大的弊端,弊端出现在注册函数register_chrdev上,其实现中有这么一句:
cd = __register_chrdev_region(major, 0, 256, name);
其向内核连续注册了256个此设备号,也就把major这个主设备号下的所有此设备号都占用了,而其实我们大部分情况下用不到这么多次设备号,造成了巨大的浪费。
于是有了这样两个函数:
① register_chrdev_region(dev_t from, unsigned count, const char *name)
② alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
register_chrdev_region函数的第一个参数dev_t from是一个unsigned型的数,高12位表示主设备号,低20位表示
次设备号。因此这个参数中包含了起始的次设备号。第二个参数unsigned count表示需要注册的连续的次设备号的个
数。最后一个参数表示设备或驱动的名字。在解释alloc_chrdev_region函数的参数之前先解释一下,这2个函数的区别和用法。register_chrdev_region这个函数在调用之前需要确定from这个参数的值,就是说设备号是已知的。而alloc_chrdev_region这个函数调用的时候不需要知道设备的设备号是多少,内核会动态分配一个给他。在
分配好了之后会把设备号写到设备号变量的地址,所以我们看到在调用alloc_chrdev_region这个函数的时候它的第一个参数是设备号变量的地址。alloc_chrdev_region它的第二个参数是起始次设备号,第三个参数是要注册的次设备号个数,最后一个是设备或驱动的名字。
这两个函数最终调用的都是__register_chrdev_region,同时需要跟cdev_init(struct cdev *cdev, const struct
file_operations *fops)和 cdev_add(struct cdev *p, dev_t dev, unsigned count)来配合使用。这2个函数的功能在以前的字符设备驱动程序中是在register_chrdev这一个函数中完成的,在这里需要我们自己来调用。这样一来,register_chrdev这个函数就被完美的替代了。我们就可以通过控制注册的起始次设备号以及多少个次设备号,实现了用多少注册多少的目的。
字符设备驱动新框架:
1、确定主设备号
主设备号已知,
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello");
主设备号未知,
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
major = MAJOR(devid);
2、构造file_operations
static struct file_operations hello_fops =
{
.owner = THIS_MODULE,
.open = hello_open,
};
3、注册
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
4、创建类和设备
hello_class = class_create(THIS_MODULE, CLASS_NAME);
device_create(hello_class, NULL, MKDEV(hello_major, 0), NULL, DEV_NAME);
一个实例程序如下:
1 #include <linux/module.h> 2 #include <linux/types.h> 3 #include <linux/fs.h> 4 #include <linux/errno.h> 5 #include <linux/mm.h> 6 #include <linux/sched.h> 7 #include <linux/init.h> 8 #include <linux/cdev.h> 9 #include <asm/io.h> 10 #include <asm/system.h> 11 #include <asm/uaccess.h> 12 #include <linux/device.h> 13 14 #define HELLO_MAJOR 0 //预设的主设备号 15 #define START_MINOR 0 //起始次设备号 16 #define DEV_CNT 2 //要注册的设备数 17 #define DRV_CNT 2 //要注册的驱动数 18 #define DRIVER_NAME "hello" //驱动名 19 #define CLASS_NAME "hello_drv" //类名 20 #define DEV_NAME "hello%d" //设备名 21 22 static int hello_major = HELLO_MAJOR; 23 24 struct class *hello_class = NULL; 25 26 //字符设备结构体 27 struct hello_dev 28 { 29 struct cdev cdev; //cdev结构体 30 void *private_data; //私有数据 31 }; 32 33 struct hello_dev *hello_devp; //设备结构体指针 34 35 static int hello_open(struct inode *inode, struct file *file) 36 { 37 printk("hello_open\n"); 38 return 0; 39 } 40 41 //文件操作结构体 42 static const struct file_operations hello_fops = 43 { 44 .owner = THIS_MODULE, 45 .open = hello_open, 46 //.read = hello_read, 47 //.write = hello_write, 48 //.ioctl = hello_ioctl, 49 }; 50 51 //设备驱动模块加载函数 52 int hello_init(void) 53 { 54 int i, res; 55 dev_t devno; 56 57 //申请次设备号范围、主设备号 58 if (hello_major) 59 { 60 devno = MKDEV(hello_major, START_MINOR); 61 res = register_chrdev_region(devno, DEV_CNT * DRV_CNT, DRIVER_NAME); 62 } 63 else 64 { 65 res = alloc_chrdev_region(&devno, START_MINOR, DEV_CNT * DRV_CNT, DRIVER_NAME); 66 hello_major = MAJOR(devno); 67 } 68 69 if (res < 0) 70 { 71 return res; 72 } 73 74 //动态申请设备结构体内存 75 hello_devp = kmalloc(DRV_CNT * sizeof(struct hello_dev), GFP_KERNEL); 76 if (hello_devp == NULL) 77 { 78 res = - ENOMEM; 79 goto fail_malloc; 80 } 81 memset(hello_devp, 0, DRV_CNT * sizeof(struct hello_dev)); 82 83 for (i = 0; i < DRV_CNT; i++) 84 { 85 cdev_init(&hello_devp[i].cdev, &hello_fops); 86 hello_devp[i].cdev.owner = THIS_MODULE; 87 cdev_add(&hello_devp[i].cdev, MKDEV(hello_major, i * DEV_CNT), DEV_CNT); 88 } 89 90 //自动创建设备节点 91 hello_class = class_create(THIS_MODULE, CLASS_NAME); 92 93 for (i = 0; i < DEV_CNT * DRV_CNT; i++) 94 { 95 device_create(hello_class, NULL, MKDEV(hello_major, i), NULL, DEV_NAME, i); 96 } 97 98 return 0; 99 100 fail_malloc: 101 unregister_chrdev_region(devno, DEV_CNT); 102 return res; 103 } 104 105 //模块卸载函数 106 void hello_exit(void) 107 { 108 int i; 109 110 cdev_del(&hello_devp->cdev); 111 kfree(hello_devp); 112 unregister_chrdev_region(MKDEV(hello_major, 0), DEV_CNT * DRV_CNT); 113 for (i = 0; i < DEV_CNT * DRV_CNT; i++) 114 { 115 device_destroy(hello_class, MKDEV(hello_major, i)); 116 } 117 class_destroy(hello_class); 118 } 119 120 module_init(hello_init); 121 module_exit(hello_exit); 122 MODULE_LICENSE("GPL"); 123 MODULE_AUTHOR("Lcm");
程序中,一个主设备号和一个次设备号共同对应一个设备节点,例子中有两个c_dev结构体,且对应同一个主设备号,每个c_dev结构体对应两个次设备号,即创建两个字符设备。