Linux字符设备驱动的开发流程是:定义设备号、定义file_operations操作集、实现open/read/write/release等操作函数、在模块初始化函数中注册字符设备、在模块退出函数中注销设备。应用程序通过设备节点文件访问驱动,内核通过copy_to_user和copy_from_user完成用户态和内核态之间的数据交互。
字符设备按字节流访问,是Linux驱动中最基础的一类
核心数据结构是cdev和file_operations
设备号 = 主设备号(标识驱动)+ 次设备号(标识具体设备)
用户态和内核态数据交互用copy_to_user和copy_from_user
面试题
请讲一下Linux字符设备驱动的开发流程,并写一个最简单的LED字符驱动框架。
包括设备号分配、注册注销、操作函数实现,以及应用层怎么调用驱动。
面试官考察点
是否理解Linux驱动的基本框架和概念
是否知道字符设备的注册和注销流程
是否能写出基本的驱动代码框架
是否理解用户态和内核态的交互方式
回答思路
字符设备驱动开发第一步要做什么?
第一步是确定设备号。设备号是驱动和设备之间的桥梁,由主设备号和次设备号组成。主设备号用来区分不同类型的驱动,次设备号用来区分同一驱动下的多个设备。设备号可以静态指定,也可以让内核动态分配。一般推荐用动态分配,就是调用alloc_chrdev_region函数,让内核给我们分配一个空闲的主设备号,避免和系统中已有的驱动冲突。
然后呢?怎么把操作函数和设备关联起来?
接下来要定义file_operations结构体,把我们实现的open、read、write、release这些函数的函数指针填进去。这个结构体就像是驱动的操作说明书,告诉内核当应用程序调用open的时候应该执行哪个函数,调用read的时候执行哪个函数。结构体里的函数指针可以部分实现,不需要的可以设成NULL。
cdev是什么?什么时候用?
cdev是内核中表示字符设备的结构体。有了设备号和操作函数之后,就要创建一个cdev对象,把它和设备号、file_operations关联起来,然后用cdev_add函数注册到内核里。注册成功之后,这个字符设备就可以被访问了。流程是:cdev_alloc分配 → cdev_init初始化 → cdev_add注册。
模块的入口和出口函数呢?
驱动是以模块的形式加载到内核的,所以需要用module_init宏指定初始化函数,用module_exit指定退出函数。初始化函数里做设备号分配、cdev注册这些事情。退出函数里要做相反的操作,先用cdev_del注销cdev,再用unregister_chrdev_region释放设备号。这样模块加载时初始化,卸载时清理资源。
代码框架示例
一个最简单的字符设备驱动框架大概是这样的:
这只是个简化的框架,实际驱动中还要处理返回值错误检查、并发控制、中断处理等等。面试的时候能写出这个框架和关键函数名,基本就能说明你掌握了基本流程。
完整的开发步骤总结
| 步骤 | 操作 | 核心函数 |
|---|---|---|
| 1 | 分配设备号 | alloc_chrdev_region 或 register_chrdev_region |
| 2 | 创建并初始化cdev | cdev_alloc + cdev_init |
| 3 | 注册字符设备 | cdev_add |
| 4 | 实现操作函数 | open / read / write / release等 |
| 5 | 指定模块入口出口 | module_init / module_exit |
| 6 | 编译加载测试 | insmod / mknod / 应用程序测试 |
面试官追问
追问1:为什么不能直接在内核里操作用户空间的指针?
因为用户空间的地址是虚拟地址,而且用户空间的内存可能被换出、可能无效,直接访问会导致内核崩溃。所以必须用copy_to_user和copy_from_user这两个函数来做数据拷贝。这两个函数会检查地址的合法性,还能处理缺页异常,如果用户地址非法会返回错误,不会导致内核挂掉。这是内核编程的基本安全原则之一。
追问2:字符设备和杂项设备有什么区别?
杂项设备misc device其实是字符设备的一种封装,主设备号固定是10,不同的杂项设备用次设备号区分。用杂项设备的好处是不用自己管主设备号,自动创建设备节点也方便,代码量比自己注册字符设备少。适合一些简单的、不需要自己独立主设备号的驱动,比如LED、按键、蜂鸣器这些。复杂的驱动还是用普通字符设备更灵活。
追问3:platform总线和字符设备什么关系?
platform总线是Linux内核里的一种虚拟总线,用来管理SOC上的片上外设。字符设备是对用户空间的接口。两者是不同层面的东西:platform驱动负责探测和管理硬件设备,字符设备负责向用户空间提供操作接口。一个platform驱动里面可以注册一个或多个字符设备,也可以注册其他类型的设备。简单说就是platform管硬件,字符设备管用户接口。
追问4:ioctl函数是干什么用的?
ioctl是input output control的缩写,用来做一些read和write不好表达的控制操作,比如设置参数、配置模式、查询状态等等。read和write只能顺着读写数据,ioctl可以传命令码和参数,更灵活。比如LED驱动里可以用ioctl来设置闪烁频率,ADC驱动里用ioctl设置采样率。2.6.36以后内核里ioctl的接口名字改成了unlocked_ioctl,不用大内核锁了。
失分表达
"直接用memcpy拷贝用户数据"——必须用copy_to_user/copy_from_user
"主设备号随便写一个就行"——要动态分配或查文档,不能冲突
"cdev_init和cdev_add是一回事"——一个是初始化结构体,一个是注册到内核
"字符设备只能控制一个硬件"——用次设备号可以管理多个设备
准备动作
手写过至少一个完整的字符设备驱动,能背出基本框架
熟悉file_operations结构体中常用的操作函数
理解用户态和内核态的区别,知道为什么要用copy_to_user
了解platform驱动框架和设备树的基本概念
准备一个实际项目中的驱动开发案例
核心关键词
字符设备驱动、cdev、file_operations、设备号、copy_to_user、module_init、insmod、platform总线、杂项设备、ioctl

扫码关注





























