/dev目录下执行ls -l
设备文件项的最后修改日期前的用逗号分割的两个数,对设备文件来说就是相应的主设备号和次设备号。
第一个字符c表示字符设备,b表示块设备
主设备号标识设备对应的驱动程序,次设备号由内核使用,用于正确确定设备文件所指的设备。依赖于驱动程序的编写方式,我们可以通过次设备号获得一个指向内核设备的直接指针,也可将次设备号当作设备本地数组的索引。
设备编号的内部表达
在内核中dev_t类型(在<linux/types.h>中定义)用来保存设备编号,包括主设备号和次设备号。dev_t是一个32位数,12位用来表示主设备号,20位用来表示次设备号。
设备号相关宏(在<linux/kdev_t.h>中定义):
MAJOR(dev_t dev); #获取主设备号
MINOR(dev_t dev); #获取次设备号
MKDEV(int major, init minor); #主设备号和次设备号转换成dev_t类型
分配和释放设备编号
头文件<linux/fs.h>
静态分配
int register_chrdev_region(dev_t from, unsigned count, const char *name);
用于生成静态指定的设备编号
from:要分配的设备编号范围的起始值
count:请求的连续设备编号的个数
name:和该编号范围关联的设备名称(/proc/devices)
成功返回0
动态分配
int alloc_chrdev_region(dev_t dev, unsigned int firstminor, unsigned int count, char *name);
dev:调用成功将返回已分配范围的第一个设备编号
firstminor:要使用的被请求的第一个次设备号,通常为0
count:请求的连续设备编号的个数
name:和该编号范围关联的设备名称(/proc/devices)
释放
void unregister_chrdev_region(dev_t from, unsigned count);
在模块的清除函数中调用
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
static int major = 277;
static int minor = 0;
static dev_t devnum1;
static dev_t devnum2;
static int hello_init(void)
{
printk("hello_init\n");
devnum1 = MKDEV(major,minor);
printk("major:%d, minor:%d\n", MAJOR(devnum1), MINOR(devnum1));
if (register_chrdev_region(devnum1, 1, "hello1"))
{
printk("register_chrdev_region failed\n");
return -1;
}
if (alloc_chrdev_region(&devnum2, 0, 1, "hello2"))
{
printk("alloc_chrdev_region failed\n");
return -1;
}
printk("major:%d, minor:%d\n", MAJOR(devnum2), MINOR(devnum2));
return 0;
}
static void hello_exit(void)
{
printk("hello_exit\n");
unregister_chrdev_region(devnum1, 1);
unregister_chrdev_region(devnum2, 1);
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
cat /proc/devices查看主设备号和设备名称,静态分配需要确保主设备号没有被注册。
驱动程序需要将设备编号和内部函数连接起来,内部函数用来实现设备的操作。
scull_load脚步用于在/dev目录创建设备文件:装载模块、获取主设备号、创建设备文件、改变设备文件的组和访问权限。
mknod /dev/设备文件名 c 主设备号 次设备号 #创建设备文件
引导阶段的启动脚步:在运行级别的目录(如 rc3.d)创建一个指向该脚步的链接。