前言
驱动的设备挂载和卸载是十分重要的内容,一旦操作不当可能会导致系统崩溃,接下来我将用字符设备的驱动挂载原理进行详细讲解,并把自己在学习过程中遇到的问题与解决办法进行展示。
设备号
设备号的组成
为了方便管理,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分 组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备 (后面有关杂项MISC的平台开发是根据不同的次设备号实现的)。 Linux 提供了 一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux 系统中主设备号范围为 0~4095,所以大家在选择主设备号的时候一定不要超过这个范围。在文 件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
其中:
宏 MINORBITS 表示次设备号位数,一共是 20 位。
宏 MINORMASK 表示次设备号掩码。
宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。
设备号的分配
静态分配
注册字符设备的时候需要给设备指定一个设备号,这个设备号可以是驱动开发者静态的指定一个 设备号,比如选择 200 这个主设备号。有一些常用的设备号已经被 Linux 内核开发者给分配掉 了,具体分配的内容可以查看文档 Documentation/devices.txt。并不是说内核开发者已经分配掉 的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个 主设备号,使用 “cat /proc/devices” 命令即可查看当前系统中所有已经使用了的设备号。
动态分配
静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用 的。而且静态分配设备号很容易带来冲突问题,Linux 社区推荐使用动态分配设备号,在注册字 符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。 卸载驱动的时候释放掉这个设备号即可。
设备号的申请函数如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:
dev:保存申请到的设备号。
baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这 些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递 增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count:要申请的设备号数量。
name:设备名字。
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
此函数有两个参数:
from:要释放的设备号。
count:表示从 from 开始,要释放的设备号数量。
驱动挂载与卸载
输入如下命令加载 chrdevbase.ko 驱动文件:
insmod chrdevbase.ko
//或者
modprobe chrdevbase.ko
如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉 chrdevbase 这 个设备:
rmmod chrdevbase.ko
驱动挂载或者卸载之后可以通过下面这两个指令查看
lsmod
cat /proc/devices
此时这里只是设备被挂载了,有对应的设备号,但是不一定能自动创建节点,驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。关于是否创建了节点可以看后面的内容)
一般情况下我们都会在驱动函数里面配置好设备的节点,如果没有配置好节点的话需要我们手动配置,手动配置流程如下:输入如下命令创建/dev/chrdevbase 这个设备节 点文件:
mknod /dev/chrdevbase c 200 0
其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个 字 符 设 备 ,“ 200 ” 是 设 备 的 主 设 备 号 ,“ 0 ” 是 设 备 的 次 设 备 号 。 创 建 完 成 以 后 就 会 存 在 /dev/chrdevbase 这个文件,可以使用 “ls /dev/chrdevbase -l” 命令查看。
设备节点创建
字符设备驱动开发重点是使用 register_chrdev 函数注册字符设备,当不再使用设备的时候就使用 unregister_chrdev 函数注销字符设备, 当使用这两个函数时,驱动模块加载成功以后还需要手动使用 mknod 命令创建 设备节点。 register_chrdev 和 unregister_chrdev 这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数,而是使用 Linux 内核推荐的新字符设备驱动 API 函数。关于设备节点的具体内容如下:
mdev机制:设备的节点自动创建主要是依赖于这个机制。
udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检 测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用 modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用 rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。 使用 busybox 构建根文件 系统的时候,busybox 会创建一个 udev 的简化版本—mdev,所以在嵌入式 Linux 中我们使用mdev 来实现设备节点文件的自动创建与删除, Linux 系统中的热插拔事件也由 mdev 管理,在 /etc/init.d/rcS 文件中如下语句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
上述命令设置热插拔事件由 mdev 来管理,关于 udev 或 mdev 更加详细的工作原理这里就 不详细探讨了,我们重点来学习一下如何通过 mdev 来实现设备文件节点的自动创建与删除。
自动创建设备节点需要经过以下过程
- 1、手动或者自动创建一个设备号
- 2、使用 cdev 结构体表示一个字符设备,在 cdev 中有两个重要的成员变量:ops 和 dev,这两个就是字符设备文件操作函数集合 file_operations 以及设备号 dev_t。
- 3、在 cdev_add 函数后面添加自动创建设备节点相关代码。这一步开始才是跟自动创建设备节点相关的内容,前面两步是铺垫。
- 4、首先要创建一个 class 类,class 是个结构体。
- 5、在这个类下创建一个设备。使用 device_create 函数在类下面创建设备。
通过以上步骤,设备的节点便可实现自动挂载,挂载至/dev目录下。
驱动挂载出现问题
在做一次按键扫描的驱动阻塞实验时,发现把设备名称改为blockio时,通过modprobe可以将设备挂载成功,但是不能通过一个app程序去运行这个节点,经过查找原因,发现/dev目录下的blockio文件不是字符类型的设备,而是块类型设备,具体是什么设备可以通过brw-rw----前面的b和c推断出来,随后我用手动创建节点的方式将这个节点强制变为字符设备的形式在去用app程序运行这个节点便可以成功。具体的过程可以见下图:
当我们名称里面不含block时,自动生成的节点都是字符类型的可以运行,可能是block这个词汇比较敏感导致的。