记得内核里是用面向对象的思想来实现的。但不是完全面向对象.
在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函数.