最近在学习字符设备驱动,涉及到很多框架层面的东西,这里就来记录一下。
概述
开始,就先把框架图放出来。
在用户空间通过
insmod
调用module_init
模块加载函数激活相应的设备驱动初始化函数- 接着就是添加字符设备驱动
- 在字符设备驱动初始化前,先要分配主次设备号
- 在对应目录中创建相应的类文件和设备文件
- 并填充
file_operations
结构体
- 字符设备驱动注册到内核后就可以使用之前应用层的
read
、write
、ioctl
等函数- 应用层所使用的对文件进行读写操作的函数都绑定了
file_operations
中的方法
- 应用层所使用的对文件进行读写操作的函数都绑定了
应用层调用接口
常用方法:
open
打开设备文件read
读取设备文件内容write
写入设备文件内容ioctl
进行IO操作close
关闭设备文件
设备类
struct kobject
数据结构在sysfs中代表一个目录struct driver
、struct device
、struct class
均由kobject
派生struct driver_attribute
、struct device_attribute
、struct class_attribute
代表普通文件struct kset
是struct kobject
的容器,可以成为同一类struct kobject
的父亲,而其自身也有struct kobject
成员,因此其又可以和其他struct kobject
成为上一级struct kset
的子成员
数据结构
cdev
- 这个是存放字符设备驱动的相关数据的结构体
|
|
file_operation
- 定义字符设备驱动提供给
VFS
的接口函数集
|
|
创建过程
- 由于年代的变迁,字符设备号的分配接口有新的和旧的,不过它们的底层还是调用了相同的函数
1. 分配字符设备号(旧接口)
① 分配
|
|
- 传入参数分别为主设备号、设备名称、文件操作集
② 释放
|
|
- 传入参数分别为主设备号、设备名称
2. 分配字符设备号(新接口)
① 静态分配
|
|
- 传入参数分别为设备号、设备数量、设备名称
② 动态分配
|
|
- 传入参数分别为设备号、次设备号基址、设备数量、设备名称
③ 释放
|
|
- 传入参数分别为设备号、设备个数
3. 创建设备文件
- 可以在应用层使用命令行
cat /proc/devices
查看所有设备的设备名、以及主次设备号
① 手工创建
mknod filename type major minor
② 自动创建
⑴ class_create
- 首先创建一个设备类
|
|
⑵ device_create
- 接着创建一个设备
|
|
- 在驱动代码中调用
class_create()
为设备创建一个设备类,再为每个设备调用device_create()
创建对应的设备,udev(mdev)会自动创建一个设备文件
原理:利用
udev(mdev)
来实现设备文件的自动创建,由busybox配置。在加载模块的时候,用户空间的mdev会自动去/sysfs
下相应的目录寻找对应的类从而创建设备结点
|
|
⑶ class_destroy
- 删除设备类
|
|
⑷ device_destroy
- 删除设备
|
|
4. 注册/注销字符设备驱动
① cdev_alloc
- 获取一个字符设备结构体
|
|
② cdev_init
- 字符设备驱动初始化
- 绑定字符设备结构体(cdev)与文件操作集(fops)
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
③ cdev_add
- 添加字符设备驱动
- 绑定字符设备驱动(cdev)与设备号(dev)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
④ cdev_del
- 注销字符设备驱动
void cdev_del(struct cdev *p)
5. 内核的虚拟地址映射
若需要用到gpio等资源就需要虚拟地址映射
① request_mem_region
- 向内核请求需要映射的内存资源
request_mem_region(start,n,name)
② ioremap
- 映射传入的物理地址返回一个虚拟地址
ioremap(cookie,size)
③ iounmap
- 传入虚拟地址,取消地址映射
iounmap(cookie)
④ release_mem_region
- 释放内核请求需要映射的内存资源
release_mem_region(start,n)
6. Others
① printk
- 内核信息打印函数
|
|
- 使用命令行
cat /proc/sys/kernel/printk
显示: 4 4 1 7 - 分别表示
- 当前控制台日志级别
- 未明确指定日志级别的默认信息日志级别
- 最高允许设置的控制台日志级别
- 引导时默认的日志级别
dmesg
可查看printk
打印的信息
② copy_from_user
- 使用
file_operations
中的方法集write
函数将数据从用户空间复制到内核空间 - `static inline unsigned long __must_check copy_from_user(void *to,
const void __user *from, unsigned long n)`
③ copy_to_user
- 使用
file_operations
中的方法集read
函数将数据从内核空间复制到用户空间 - `static inline unsigned long must_check copy_to_user(void user *to,
const void *from, unsigned long n)`
注:本文内容部分来自互联网整理,部分来自个人经验总结;本文将持续收集更新,欢迎留言补充!