1. 基本步骤
(1)确定主设备号和次设备号
(2)实现字符驱动程序
- 实现file_operations结构体;
- 实现初始化函数,注册字符设备;
- 实现销毁函数,释放字符设备;
- 实现字符设备其他基本成员函数。
(3)创建设备文件节点
2. 什么是主设备号/次设备号
主设备号是内核识别一个设备的标识。它是一个整数(占12位),通常使用1~255。
次设备号由内核使用,用于正确确定设备文件所指的设备。它也是一个整数(占20位),通常使用0~255。
注:同一类设备的主设备号相同,不同的是次设备号。如多个串口的主设备号是相同的,次设备号不同。
3. 设备编号的内部表达
(1)dev_t类型(32位):用来保存设备编号
(2)从dev_t获得主、次设备号:
- MAJOR(dev_t): //主
- MINOR(dev_t); //次
(3)将主、次设备号转换成dev_t类型:
- MKDEV(int major, int minor);
4. 分配设备号
通常在模块加载函数中调用。
(1)手工分配:找一个内核没有使用的主设备号来使用
#include<linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
//first是设备号,次设备号通常为0;count是要分配设备号的个数,即次设备号个数;name是此类设备的名字。
(2)动态分配:
#include<linux/fs.h>
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
//dev是输出的设备号;firstminor是要申请第一个次设备号;count是要申请的设备号个数;name是此类设备的名字。
5. 释放设备号
通常在模块清理函数中调用。
#include<linux/fs.h>
void unregister_chrdev_region(dev_t dev, unsigned int count);
6. 重要结构体
(1)cdev结构体
struct cdev操作cdev结构体的函数:
{
struct kobject kobj; //内嵌的kobject对象
struct module *owner; //所属模块
struct file_operations *ops; //文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
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,完成字符设备的注销,通常在模块卸载函数中调用
(2)file_operations结构体
- 是字符驱动和内核的接口,在include/linux/fs.h中定义;
- 字符驱动都要实现一个file_operations结构体;
file_operations的主要成员:
struct module *owner:指向模块自身(THIS_MODULE)
open:打开设备
release:关闭设备
read:从设备上读数据
write:向设备上写数据
ioctl:I/O控制函数
llseek:定位当前读写位置指针
mmap:映射设备空间到进程的地址空间
(3)file结构体
- file_operations结构相关的一个结构体,描述一个正在打开的设备文件。
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结构在内部表示文件;
- inode与file的区别:file表示打开的文件描述符,多个表示打开的文件描述符的file结构可以指向一个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); //注销设备
...
}