内核模块读取设备的实现方法及设备存在性校验相关问题
内核模块中读取/检测字符设备的实现方案
基础说明
内核空间无法直接调用用户态的
open、read等文件API,但可以直接调用内核原生接口访问设备,不需要依赖/dev下的文件节点。如果是示例中的块设备(如/dev/sda),访问逻辑和字符设备不同,需要通过struct block_device体系的接口操作,以下内容均针对你提到的字符设备使用场景。
设备存在性校验
两种常用校验方法,可以不需要读取设备就确认设备是否存在:
- 已知设备主、次设备号的场景:调用
chrdev_exists(MKDEV(major_num, minor_num)),返回非0值表示设备存在,返回0表示设备不存在 - 已知设备驱动注册名称的场景:通过
find_cdev()系列接口遍历内核字符设备映射表,匹配对应名称的struct cdev实例,匹配成功则设备存在
直接读取字符设备的操作步骤
不需要走/dev文件节点,直接通过驱动接口完成读取:
- 先通过上述校验方式获取目标字符设备的设备号
dev_t或者struct cdev实例指针 - 从
struct cdev中获取驱动注册的文件操作集struct file_operations *fops - 处理uaccess适配:大部分驱动的read接口会使用
copy_to_user()等用户态地址校验函数,内核态地址默认会被这类函数拦截,你需要:- 旧内核(4.x及更早):调用
set_fs(KERNEL_DS)临时修改当前进程的地址空间上限,允许uaccess函数访问内核地址,操作完成后调用set_fs(USER_DS)恢复 - 新内核(5.0及以上):调用
force_uaccess_begin()获取原有标识,操作完成后调用force_uaccess_end(原有标识)恢复
- 旧内核(4.x及更早):调用
- 直接调用
fops->read()接口传入内核态缓冲区地址,完成设备读取 - 操作完成后释放所有申请的锁、临时资源,恢复系统原有状态
注意事项
- 不要硬编码主、次设备号,不同环境下设备号可能发生变化,优先通过驱动名称匹配获取正确的设备号
- 调用驱动接口前需要持有对应设备的互斥锁,避免和用户态的设备操作产生并发冲突,引发数据错误或者内核崩溃
- 该操作属于高危内核操作,一旦参数传错或者触发驱动异常会直接导致系统panic,建议先在隔离的测试环境充分验证逻辑正确性
内容的提问来源于stack exchange,提问作者Harry Muscle
相关产品推荐
相关产品推荐

