记得内核里是用面向对象的思想来实现的。但不是完全面向对象. 在linux内核里使用”struct cdev”类型的一个对象来描述一个字符设备驱动。
#include <linux/cdev.h> struct cdev { struct kobject kobj; //内核用于管理字符设备驱动, kobject就是内核里最底层的类. 内核里会自动管理此成员. struct module *owner; //通常设为THIS_MODULE, 用于防止驱动在使用中时卸载驱动模块 const struct file_operations *ops; //怎样操作(vfs), 也就是实现当用户进程进行open/read/write等操作时,驱动里对应的操作. struct list_head list; //内核链表节点,内核里自动管理此成员. dev_t dev; //设备号 unsigned int count; //设备数 }; 同时内核里也提供对cdev对象操作的函数: void cdev_init(struct cdev *, const struct file_operations *); //初始化cdev对象 struct cdev *cdev_alloc(void); //动态分配一个cdev对象 int cdev_add(struct cdev *, dev_t, unsigned); //设置cdev对象使用设备号及设备个数, 再把cdev对象增加到内核里,让它工作起来. void cdev_del(struct cdev *); //从内核里移除cdev对象字符设备驱动实现的基本流程//
1. 申请设备号 register_chrdev_region(...); 2. 声明一个cdev对象 struct cdev mycdev; 声明一个file_operations的文件操作对象 struct file_operations fops = { .owner = THIS_MODULE, .read = 读函数地址 .... }; 3. 初始化cdev对象,并把fops对象与cdev对象关联起来 cdev_init(&mycdev, &fops); //mycdev.ops = &fops; mycdev.owner = THIS_MODULE; 4. 把cdev对象加入内核里cdev_map(字符设备驱动的哈希表), 并指定该驱动对象的设备号 cdev_add(&mycdev, 设备号, 次设备号的个数); 5. 卸载模块时, 要把设备驱动对象从内核里移除, 并把设备号反注册 unregister_chrdev_region(..); cdev_del(&mycdev);测试代码test.c:
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #define MYMA 1234 #define MYMI 3344 #define COUNT 3 dev_t devid; //用于存放设备号 struct cdev mycdev; int myopen(struct inode *ind, struct file *fl) { printk("in %s\n", __func__); return 0; } ssize_t myread(struct file *fl, char *__user buf, size_t len, loff_t *off) { printk("in %s\n", __func__); return 0; } ssize_t mywrite(struct file *fl, const char __user *buf, size_t len, loff_t *off) { printk("in %s\n", __func__); return len; } struct file_operations fops = { .owner = THIS_MODULE, .open = myopen, .read = myread, .write = mywrite, }; static int __init test_init(void) { int ret; devid = MKDEV(MYMA, MYMI); //生成一个设备号 ret = register_chrdev_region(devid, COUNT, "mydev"); if (ret < 0) goto err0; //执行到这里,则有三个设备号(1234,3344), (1234, 3345), (1234, 3346) cdev_init(&mycdev, &fops); mycdev.owner = THIS_MODULE; ret = cdev_add(&mycdev, devid, COUNT); if (ret < 0) goto err1; return 0; err1: unregister_chrdev_region(devid, COUNT); err0: return ret; } static void __exit test_exit(void) { //使用完后需回收设备号 unregister_chrdev_region(devid, COUNT); cdev_del(&mycdev); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL");/// 编译加载驱动模块后,需要用”mknod /dev/设备文件名 c 主设备号 次设备号”来创建设备文件.
mknod /dev/mydev0 c 1234 3344 mknod /dev/mydev1 c 1234 3345 mknod /dev/mydev2 c 1234 3346 然后可以写应用程序来操作设备文件,也可以用命令来简单地测试。 cat /dev/mydev0 //会触发驱动里的open, read函数 echo "kkk" > /dev/mydev0 //会触发驱动里的open, write函数.