本文系作者原创, 欢迎大家转载!
转载请注明出处:netwalker.blog.chinaunix.net
===========================================
《创建字符设备的三种方法》这篇文章已经在网上广为流传,你可以在http://blogold.chinaunix.net/u1/36290/showart_415402.html找到这篇文章的一份拷贝,从浏览量上看定有许多人参考过这篇文章,但是很遗憾,它的第三种方法有一个严重的问题,内存泄露!虽然大多数时候它跑的很好,而内存泄露导致系统崩溃也是几无可能,但是这是一个模板,如果大多数人在写驱动时参考了这篇文章,并且不巧选择了第三种,那么潜在危险就有点大了,好了言归正传。
3.使用udev在/dev/下动态生成设备文件的方式
1. #include<linux/kernel.h>
2. #include<linux/module.h>
3. #include<linux/types.h>
4. #include<linux/fs.h>
5. #include<linux/cdev.h>
6. #include<linux/pci.h>
7. #include<linux/moduleparam.h>
8. #include<linux/init.h>
9. #include<linux/string.h>
10. #include<asm/uaccess.h>
11. #include<asm/unistd.h>
12. #include<asm/uaccess.h>
13. MODULE_LICENSE("GPL"); /*此处如果不加的话加载的时候会出错*/
14. int init_module(void);
15. void cleanup_module(void);
16. static int device_open(struct inode*, struct file*);
17. static int device_release(struct inode*, struct file*);
18. static ssize_t device_read(struct file*, char *, size_t, loff_t*);
19. static ssize_t device_write(struct file*, const char*, size_t, loff_t*);
20. #define SUCCESS 0
21. #define DEVICE_NAME "chardev"
22. #define BUF_LEN 80
23.
24. static int major;
25. static int Device_open = 0;
26. static char msg[BUF_LEN];
27. static char *msg_ptr;
28. static struct cdev *my_cdev;
29. static struct class *my_class;
30. dev_t devid ;
31. static struct file_operations fops =
32. {
33. .read = device_read,
34. .write = device_write,
35. .open = device_open,
36. .release = device_release,
37. };
38. int init_module(void)
39. {
40. int err;
41. alloc_chrdev_region(&devid, 0, 1, "chardev");
42. major = MAJOR(devid);
43. my_cdev = cdev_alloc();
44. cdev_init(my_cdev, &fops);
45. my_cdev->owner = THIS_MODULE;
46. err = cdev_add(my_cdev, devid, 1);
47. if (err)
48. {
49. printk(KERN_INFO "I was assigned major number %d.\n", major);
50. return -1;
51. }
52. my_class = class_create(THIS_MODULE, "chardev_class1");
53. if (IS_ERR(my_class))
54. {
55. printk(KERN_INFO "create class error\n");
56. return -1;
57. }
58. class_device_create(my_class, NULL, devid, NULL, "chardev" "%d", MINOR(devid));
59. printk("major number is %d\n", MAJOR(devid));
60. return SUCCESS;
61. }
62. void cleanup_module(void)
63. {
64. cdev_del(my_cdev);
65. class_device_destroy(my_class, devid);
66. class_destroy(my_class);
67. printk("cleanup done\n");
68. }
69. ......
my_cdev = cdev_alloc();cdev_init(my_cdev, &fops);
注意到上面两句话,申请cdev设备,然后调用cdev_init,然后通过cdev_add添加到内核链表中,没错啊,顺理成章,但是如果你仔细看过LDD3,那么你一定没有见过书上这样用,它的用法是:
1. cdev = cdev_alloc();
2. cdev->owner = THIS_MODULE;
3. cdev->ops = fops;
问题的根本就是,一个用了cdev_init,一个没有。比较一下和cdev_alloc和cdev_init的代码:
1. struct cdev *cdev_alloc(void)
2. {
3. struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
4. if (p) {
5. INIT_LIST_HEAD(&p->list);
6. kobject_init(&p->kobj, &ktype_cdev_dynamic);
7. }
8. return p;
9. }
1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)
2. {
3. memset(cdev, 0, sizeof *cdev);
4. INIT_LIST_HEAD(&cdev->list);
5. kobject_init(&cdev->kobj, &ktype_cdev_default);
6. cdev->ops = fops;
7. }
注意到红色部分,这里需要了解一下内核kobject的工作机制,它们像容器一样对内核中的设备进行统一的管理,可以参考网络中的相关文章,这里不做详解。
注意到kobject_init的第二个参数struct kobj_type *ktype,其中的release函数会在cdev_del时被调用:
1. static struct kobj_type ktype_cdev_default = {
2. .release = cdev_default_release,
3. };
4.
5. static struct kobj_type ktype_cdev_dynamic = {
6. .release = cdev_dynamic_release,
7. };
继续追踪这两个函数的实现:
1. static void cdev_default_release(struct kobject *kobj)
2. {
3. struct cdev *p = container_of(kobj, struct cdev, kobj);
4. cdev_purge(p);
5. }
6.
7. static void cdev_dynamic_release(struct kobject *kobj)
8. {
9. struct cdev *p = container_of(kobj, struct cdev, kobj);
10. cdev_purge(p);
11. kfree(p);
12. }
注意到红色部分的kfree。回顾前文,如果使用cdev_alloc动态申请设备空间,此时释放函数被安装为 cdev_dynamic_release,到这里还都没错,然后调用cdev_init,此时释放函数被替换为了 cdev_default_release,最终模块卸载的时候,一段内存就这样偷偷溜走了!
你可以尝试在内核中搜索cdev_alloc,然后看看使用它们的地方有没有接着使用cdev_init,很遗憾,没有,如果有,那么快点报告该驱动的维护者吧,他应该看看LDD3了! ^^
世界又安静了!
附一个字符设备驱动实例:http://download.csdn.net/detail/lucien_cc/4216033