1、驱动三要素:
/*头文件*/ #include <linux/init.h> #include <linux/module.h> /*加载函数*/ module_init(); /*卸载函数*/ module_exit();
2、驱动初始化
1)申请设备号
/*静态申请*/ int register_chrdev_region(dev_t from, unsigned count, const char *name)
指定从设备号from开始,申请count个设备号,在/proc/devices中的名字为name。 返回值: 成功返回0,失败返回错误码。
/*动态申请*/ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
动态申请从次设备号baseminor开始的count个设备号,在/proc/devices中的名字为name,并通过dev指针把分配到的设备号返回给调用函数者。 返回值: 成功返回0,失败返回错误码。
2)字符设备的注册(告诉内核操作设备的函数)
注册函数用结构体struct cdev来表示一个字符设备。字符设备的注册就是要定义一个这样的结构体并且填上对应的内容。
先看一下结构体的成员,没注释的是内核自己要填的。 /*/include/linux/cdev.h*/ struct cdev { struct kobject kobj; struct module *owner; //一般初始化为THIS_MODULE const struct file_operations *ops; //文件操作结构体 struct list_head list; dev_t dev; //设备号 unsigned int count; //添加的设备个数 };
注册也分为三个步骤:
1)分配cdev;
2)初始化cdev;
3)添加cdev;
分配cdev:简单的说就是定义一个cdev结构体
方法一:直接定义: struct cdev test_cdev; 方法二:调用函数:struct cdev* cdev_alloc(void) struct cdev* test_cdev; test_cdev = cdev_alloc();
初始化cdev:将文件操作结构体添加到cdev中:
void cdev_init(struct cdev *cdev, const struct file_operations *fops) 参数: cdev:之前我定义的cdev结构体; fops:设备对应的文件操作结构体。 返回值:(函数有可能失败,查看返回值是必须的) 成功返回0,示范返回对应的错误码
这个函数干了两件事情:
1)内核自己填充了结构体中list和kobj的内容
2)把我传入的文件操作结构体也填充进去。
一般的,还要手工定义结构体成员owner。
struct file_operations test_fops; cdev_init(&test_cdev, &test_fops); test_cdev->owner = THIS_OWNER //指定模块的所属
添加cdev:将cdev结构体与设备号关联起来:
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count) 参数: cdev:指定要被添加的cdev结构体; dev:对应的设备号 count:从设备号dev开始添加count个设备. 返回值: 成功返回0,失败返回对应的错误码。
函数干了也两件事:
1)把cdev结构体中还没填充的两个成员dev和count按照传入参数赋值。
2)把cdev结构体中传入内核,这样内核就知道对应设备号和具体的文件操作结构体了。
3、实现设备操作
定义file_operation,并添加实现函数
4、驱动注销
/*删除cdev*/
void cdev_del(struct cdev *p)
使用:这是添加的逆操作,模块卸载时调用
/*释放设备号*/
void unregister_chrdev_region(dev_t from, unsigned count)
使用:释放从from开始count个设备号。
此外,还可以自动生成字符类设备节点:
首先定义
static struct class *xxx_class;
static struct class_device *xxx_class_dev;
1、创建设备类
class_create
2、释放设备类
class_destroy
3、创建字符类设备节点
class_device_create
4、摧毁设备节点
class_device_unregister