【摘要】
利用 Linux 的设备驱动模型实现设备号的自动分配和设备节点的自动创建与销毁,提高驱动代码的可移植性,减少人工管理成本。
【关键词】
设备号,设备节点,自动管理
背景
目前项目几个基于 Linux 的单板中,对于驱动设备的管理大多采用内核态写死设备号、用户态调用 mknod
创建设备节点的方式。
采用设备号写死的方式,在没有整理出各个单板的设备号分配列表的情况下,可能由于疏忽造成同一块单板中设备号冲突,或在移植驱动代码时各个单板之间的设备号冲突。
另一方面,用户态调用 mknod
创建设备节点,需要知道内核态中分配的主次设备号再传入 mknod
,这就要求用户态代码与内核态代码一一对应,实现不了用户态驱动代码的模块化。
解决思路
Linux 的设备驱动模型为设备的管理奠定了坚实的基础。设备节点的自动管理,包括两部分:设备号的自动分配,设备节点的自动创建与销毁。
(1)设备号的自动分配:
Linux 2.6 版本引入的设备驱动模型,支持设备号的自动分配,只需在创建设备时将主设备号、次设备号指定为 0 即可。内核会根据当前系统运行情况,从空闲的主次设备号分配一对给此设备使用,并将分配好的主次设备号、设备类型等保存在 /sys
文件系统中。
(2)设备节点的自动创建与销毁:
同样在 Linux 2.6 版本,引入了 udev(userspace device)实用程序,它以守护进程的形式运行,当设备驱动加载、卸载时,它会掌握主设备号、次设备号,以及设备类型,而后在 /dev
目录下自动创建、销毁设备节点文件。
udev 的工作原理大致如下:在上述“设备号的自动分配”小节中,内核不仅将设备的主次设备号、设备类型保存在 /sys
文件系统中,同时还将这些信息组织成 uevent 消息,传递到用户态。守护进程 udev 通过监听分析内核发出的 uevent,查看注册在 /sys
目录下的设备信息,而后在 /dev
目录相应位置上创建、销毁设备节点文件。
而我们的应用中使用的是 mdev,是 BusyBox 中的实现,可以理解为 udev 的精简版。
实践情况
内核态
以 Marvell 3136 交换芯片的内核驱动为例。3136 在内核中有两个设备,第一个是 mvKernelExt,首先定义全局变量(关键是其中的 struct class
对象):
struct class* mvKernelExt_class = NULL;
const char mvKernelExt_name[] = "mvKernelExt";
在驱动的初始化函数 mvKernelExt_init()
中适当位置添加如下代码:
/* create device class */
mvKernelExt_class = class_create(THIS_MODULE, mvKernelExt_name);
if (IS_ERR(mvKernelExt_class))
{
printk("mvKernelExt_init: class_create failed for %s\n", mvKernelExt_name);
return PTR_ERR(mvKernelExt_class);
}
/* ---cut--- */
/* create device */
device_create(mvKernelExt_class, NULL, MKDEV(mvKernelExt_major, mvKernelExt_minor),
NULL, "%s", mvKernelExt_name);
第二个是 mvPP,在初始化函数 prestera_init()
中适当位置添加如下代码:
/* create device */
device_create(mvKernelExt_class, NULL, MKDEV(prestera_major, 0),
NULL, "%s", prestera_name);
可以看到,两个设备共用一个 struct class
对象(mvKernelExt_class
)。同时,将其中的 mvKernelExt_major
、mvKernelExt_minor
、prestera_major
都设置为 0.
这样,就实现了 Linux 内核态的设备号的自动分配。
上述两个关键函数 class_create()
和 device_create()
,都是为了将设备与 /sys
文件系统建立关联,以便内核自动检测 /sys
文件系统并向用户态发送 uevent 消息,辅助用户态的 mdev 完成设备节点的自动创建等工作。
用户态
(1)mdev 的启用与配置
BusyBox 中配置 mdev:在 Linux System Utilities 中选上 mdev,其下各个子项会自动选中。保存配置后重新编译 BusyBox。
/etc/init.d/rcS
文件中添加:
/bin/mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mkdir /dev/shm
mount -t tmpfs tmpfs /dev/shm
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
几个关键命令的作用如下:
/bin/mount -a
首先挂载 /etc/fstab
中定义的文件系统(包括 /proc
、/sys
等)。/etc/fstab
中主要内容如下:
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs size=64k,mode=0755 0 0
其中的 /sys
文件系统对于实现 mdev 至关重要,不可或缺。另外,在下面挂载其它文件系统之前,首先挂载了 /dev
。
mount -t devpts devpts /dev/pts
挂载 /dev/pts
,文件系统类型为 devpts,用于支持 telnet、ssh 等远程登录时的终端设备节点的动态创建。
mount -t tmpfs tmpfs /dev/shm
挂载 /dev/shm
,文件系统类型为 tmpfs,用于共享内存的设备节点的动态创建。
echo /sbin/mdev > /proc/sys/kernel/hotplug
将 hotplug 回调设置为 /sbin/mdev
,这样,在系统中有设备插拔时,就会调用 /sbin/mdev
做进一步处理。
mdev -s
手动启动一次对 /sys
文件系统的扫描并创建设备节点。
另外,需要增加 mdev 的配置文件 /etc/mdev.conf
,内容如下:
KernDrvAgt 0:0 660 =misc/
kerbbx 0:0 660 =misc/
由于 mdev 默认将设备节点创建在 /dev
目录下,上述的配置将 KernDrvAgt
和 kerbbx
两个设备节点创建在 /dev/misc
目录下,保持与实际使用情况一致,避免了修改 BSP 代码中 open 时的路径。另外两个字段分别表示设备节点所属的用户组、用户以及权限。
(2)去掉 BSP 代码中的 mknod()
函数的调用
由于创建设备节点的任务已经交由 mdev 自动完成,因此 BSP 代码中对 mknod()
函数的调用自然就可以去掉了。
这样,就实现了 Linux 用户态的设备节点的自动创建、销毁。