imx6ull-新字符设备驱动
新字符设备驱动
复盘
之前学的是内核加载模块(驱动程序),下面总结一下他的整个逻辑
关于insmod的内部具体的内核加载模块的逻辑,可以详细看看linux设备驱动程序开发这本书,里面有什么层叠技术,底层需要比对模块和内核源码的版本关系,然后开辟一段内存,把代码段拷贝到这段内存里面。且模块和内核一起共用内核栈,所以驱动程序里面也要避免使用大的临时变量,如有必要,必须要用动态内存分配,而且,驱动程序必须要主动释放占用资源。针对SMP,要保证可重入性
关于驱动程序和应用程序的一个不同点,昨天看到的。
进入驱动程序的途径,目前有两个:
- 系统调用:
- 中断
所以驱动程序的编写不是应用程序的顺序逻辑,本质上是并发编程。
之前学习的register_chrdev注册设备的api已经过时了,现在看看新版的api
新字符设备驱动原理
分配和释放设备号
register_chrdev的缺点:
- 提前知道设备号
- 主设备号表示驱动程序,次设备号表示使用这个程序的设备
- 所以主设备号绑定驱动后,次设备号全是这种设备了。浪费
一个led设备肯定只能有一个主设备号,一个次设备号。
注意,之前这里没有申请设备号这个概念,只是注册设备(默认已知设备号的前期),所以这一小节是要解决如何获取设备号的问题,注册设备在下面一小节 |
解决办法:使用设备号的时候,向linux内核申请,需要多少,就申请多少,由linux内核分配设备可以使用的设备号。
注册设备时,自动申请设备号
//没有指定设备号,用这个api来申请设备号 |
注销设备时,自动释放设备号
void unregister_chrdev_region(dev_t from, unsigned count) |
使用模板:
int major; /* 主设备号 */ |
新的字符设备注册方法
字符设备结构
linux中使用cdev结构体表示一个字符设备。定义在include/linux/cdev.h
struct cdev { |
编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备
struct cdev chrdevbase; |
cdev_init函数
前面我们定义了cdev变量,之后就要初始化。
//字符设备变量初始化 |
cdev_add函数
向 Linux 系统添加字符设备(cdev 结构体变量)(这个才是最后的注册设备,init填写操作函数集合)
int cdev_add(struct cdev *p, dev_t dev, unsigned count) |
cdev_del函数
这个肯定就是注销设备的函数了,用来从linux内核删除相应的字符设备。
void cdev_del(struct cdev *p) |
自动创建设备节点
之前,我们
- insmod驱动模块后
- 内核加载了驱动模块
- 注册了字符设备(里面绑定了设备号,操作函数)
- 还需要手动mknod创建设备节点
现在想要自动创建设备节点。在insmod/modprobe加载模块成功后,就会自动在/dev目录下创建对应的设备文件。
mdev机制
udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件.
busybox里面,可以创建udev的简化版-mdev
mdev 来实现设备节点文件的自动创建与删除,Linux 系统中的热插拔事件也由
mdev 管理
在/etc/init.d/rcS 这个系统开机启动文件中增加这一句,来设置热插拔事件由mdev来管理。
echo /sbin/mdev > /proc/sys/kernel/hotplug |
创建和删除类
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码
所以不是全自动,是驱动里面手动添加的

创建设备

可以看到,原来我们创建设备节点,只要mknod,现在要在驱动里面实现设备节点随驱动模块一起创建注销。还是相对比较复杂
要先创建类,然后再在这个类下面创建设备,这样才能实现创建设备节点
前面记录的有点乱,这里整体总结一下
总的来说,新字符设备驱动开发,主要就是3件事情:
- 申请设备号
alloc_devid_region(设备号个数,设备名称)
- 注册cdev字符设备
cdev_initcdev_add
- 创建设备节点
classdevice
先自己自定义一个结构体, 整理一下要做的事情,这样不容易乱
struct new_dev { |
下面是我自己写的一个demo,用于构建一个虚拟字符设备,以及对应的驱动程序
|
疑问回答
设备号:主号与次号的本质
主设备号(Major):代表哪一类设备。
- 内核通过主设备号来定位对应的驱动程序。你可以把它理解为“部门编号”,比如200号部门专门管LED。
次设备号(次要):代表该类设备里的哪一个。
- 如果你的板上有4个LED:0,1,2,3。
申请多个设备号:
- 当你申请
NEWCHRDEV_CNT = 1时,内核分配给你一个 (Major, Minor); - 如果你申请
10 个,内核会分配给你一个 Major,以及从Minor 到 Minor+9的连续次设备号。
- 当你申请
cdev 注册:为什么不能直接赋值?
cdev字符设备的注册,我只是cdev_init,绑定了操作函数的集合,为啥我不直接赋值cdev的那个变量?然后我就直接cdev_add注册设备了。但是我发现cdev里面还有什么kobject这些变量,为啥这些都不需要。
你疑惑为什么不直接 newchrdev._cdev.ops = &test_fops?
cdev_init的作用:- 它不仅是绑定
fops - 更重要的是初始化
cdev结构体内部的kobject。kobject是内核用来实现引用计数、层次结构(sysfs)的核心工具。
- 它不仅是绑定
隐藏的逻辑:
- 内核有很多链表和树状结构。
- cdev_init 会清零结构体并初始化内部的小零件,
- 而 cdev_add 则是正式把这个对象推入内核的“设备管理大图”中。
- 如果你手动赋值而漏掉了内部零件的初始化,内核在查找驱动时就会崩溃。
设备节点、类、设备的三角关系
为什么要设备节点?
用户空间的程序(如你的 main.c)无法直接访问内核函数。用户只认文件路径(/dev/xxx)。设备节点就是用户空间的门,内核空间的窗。
类 (Class) 与 设备 (Device):
class_create:- 在
/sys/class/目录下创建一个文件夹。这是为了告诉系统:“我这类设备叫这个名字”。
- 在
device_create:- 这步最关键。它会触发内核发送一个 “
uevent“ 信号。 - 应用层的一个守护进程(
mdev或 udev)收到信号后,会根据信息自动在/dev/目录下创建一个对应的文件。
- 这步最关键。它会触发内核发送一个 “
重名问题:类名和设备名可以重名,但在 /dev/ 下最终可见的是 device_create 里的名字。
为什么创建设备节点,既需要创建类,又需要创建设备?
简单来说:不创建 class 驱动确实能跑,但你会失去自动化带来的所有便利
/sys(sysfs) 到底是什么
/sys 挂载的是 sysfs 虚拟文件系统, 作用:
- 相当于内核的实时数据库和档案登记处
- 内容:以树状结构暴露内核里面所有的对象(kobjects),总线,驱动,类
- 如果在内核里面创建了
类,设备,sysfs就会立刻生成对应的文件夹,属性文件
class_create必须吗
因为用户程序利用系统调用,只和/dev下的设备节点打交道,为什么还要在/sys下创建类
当我们class_create,和device_create时:
- 内核发送信号:内核在sysfs中登记信息,并发送一个uevent的广播
- 用户守护进程mdev,监听到uevent信号
mdev守护进程从sysfs中读取信息(找到设备号,设备名),替你在/dev下创建节点动作。
下面具体分析这两个的过程
class_create(THIS_MODULE,NEWCHRDEV_NAME)- 指定了类名,在/sys/class下创建了一个叫new_chrdev的文件夹

device_create(newchrdev._class, NULL, newchrdev.devid, NULL,NEWCHRDEV_NAME)- 指定了类,设备号,设备名
- 在
sysfs中建档,创建名为dev的文件- dev的内容就是设备号,作为mdev查询兜底方案
- 发送uevent信号(包含环境变量)
mdev接收uevent信号,并读取环境变量ACTION=add,要创建节点DEVNAME=new_chrdev, 设备节点叫什么- 设备号
- 所属类/子系统
驱动程序 (test.c) Linux 内核 (Kernel) 用户空间 (User Space) |
设备名到底和谁绑定?
为什么我的设备名这个字符串,在申请设备号的时候用到了,在创建设备节点的时候也用到了?这个设备名到底和什么绑定在一起?就是:设备(注册的cdev),设备节点文件(类,设备),设备号(主设备号,次设备号),设备名,和我的一个具体的设备,我的外设驱动程序,他们的绑定关系是什么?另外,代码里面自己自定义了一个结构体变量,newchrdev, 这个是代表一个具体的物理设备,还是别的?
alloc_chrdev_region里的名字:出现在/proc/devices中,方便你查看主设备号分配情况device_create里的名字:出现在/dev/目录下,是用户 open 时填写的路径
驱动程序各个部分的调用绑定逻辑
用户空间 (User Space) 内核空间 (Kernel Space) 硬件 (Hardware) |
我们自定义的设备结构体要如何理解物理意义
struct new_dev newchrdev 代表的是 “软件层面的设备上下文”。
在简单的驱动里,它代表一个物理设备。
在复杂的驱动里,比如你有 4 路串口,你可能会定义一个 struct my_uart_dev,然后定义一个数组 devs[4]。
核心技巧:在
test_open里你写了filp->private_data = &newchrdev;。这是一招妙棋!这意味着在以后的 read、write 函数中,你可以直接通过
filp->private_data拿到这个结构体,从而访问到该设备的所有状态信息(如设备号、寄存器基址等),这体现了面向对象的编程思想。(你打开谁就操作谁,不用管具体是哪个变量)