1. 基本步骤
(1)确定主设备号和次设备号
(2)实现字符驱动程序
(3)创建设备文件节点
2. 什么是主设备号/次设备号
主设备号是内核识别一个设备的标识。它是一个整数(占12位),通常使用1~255。
次设备号由内核使用,用于正确确定设备文件所指的设备。它也是一个整数(占20位),通常使用0~255。
注:同一类设备的主设备号相同,不同的是次设备号。如多个串口的主设备号是相同的,次设备号不同。
3. 设备编号的内部表达
(1)dev_t类型(32位):用来保存设备编号
(2)从dev_t获得主、次设备号:
(3)将主、次设备号转换成dev_t类型:
4. 分配设备号
通常在模块加载函数中调用。
(1)手工分配:找一个内核没有使用的主设备号来使用
#include<linux/fs.h> int register_chrdev_region(dev_t first, unsigned int count, char *name); //first是设备号,次设备号通常为0;count是要分配设备号的个数,即次设备号个数;name是此类设备的名字。
#include<linux/fs.h> int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); //dev是输出的设备号;firstminor是要申请第一个次设备号;count是要申请的设备号个数;name是此类设备的名字。
通常在模块清理函数中调用。
#include<linux/fs.h> void unregister_chrdev_region(dev_t dev, unsigned int count);
(1)cdev结构体
struct cdev { struct kobject kobj; //内嵌的kobject对象 struct module *owner; //所属模块 struct file_operations *ops; //文件操作结构体 struct list_head list; dev_t dev; //设备号 unsigned int count; };操作cdev结构体的函数:
void cdev_init(struct cdev *cdev, struct file_operations *ops); //用于初始化已分配的cdev结构,并建立cdev和file_operations之间的连接 struct cdev *cdev_alloc(void); //用于动态申请一个cdev内存 int cdev_add(struct cdev *cdev, dev_t num, unsigned int count); //向内核添加一个cdev,完成字符设备的注册,通常在模块加载函数中调用 void cdev_del(struct cdev *cdev); //删除一个cdev,完成字符设备的注销,通常在模块卸载函数中调用
file_operations的主要成员:
struct module *owner:指向模块自身(THIS_MODULE)
open:打开设备
release:关闭设备
read:从设备上读数据
write:向设备上写数据
ioctl:I/O控制函数
llseek:定位当前读写位置指针
mmap:映射设备空间到进程的地址空间
(3)file结构体
file的成员:
loff_t f_pos:当前读写位置
unsigned int f_flags:标识文件打开时是否可读或可写,O_RDONLY、O_NONBLOCK、O_SYNC
struct file_operations *f_op:文件相关的操作,指向所实现的struct file_operations
void *private_data:私有数据指针,驱动程序可以将这个字段用于任何目的或者忽略(设为NULL)这个字段
(4)inode结构体
inode的重要成员:
dev_t i_rdev:对表示设备文件的inode结构,该字段包含了真正的设备号
struct cdev *i_cdev:表示字符设备在内核中的内部结构
7. 字符设备驱动程序模版
//字符设备驱动模块加载函数 static int __init xxx_init(void) { ... cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化cdev xxx_dev.cdev.owner = THIS_MODULE; //获取字符设备编号 if(xxx_major) { register_chrdev_region(xxx_dev_no, 1, DEV_NAME); } else { alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME); } ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备 ... }
//字符设备驱动模块卸载函数 static void __exit xxx_exit(void) { unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号 cdev_del(&xxx_dev.cdev); //注销设备 ... }