概述:利用regist_chrdev_region() 函数接口注册同一类字符设备的多个子设备。
上一节一起整理了一遍注册一个简单字符设备的流程,接下来就来实现一个同一类字符设备的多个子设备驱动程序。
1. Demo 程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/fs.h>
#define CDE_NAME "Rivotek_cdev"
struct my_char_dev
{
unsigned int maj; //主设备号
unsigned int mio; //次设备号
unsigned int count;
struct cdev *cdev;
};
struct my_char_dev *lcdev;
static int lcdev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO"lcdev open\n");
return 0;
}
static int lcdev_release (struct inode *inode, struct file *file)
{
printk(KERN_INFO"lcdev release\n");
return 0;
}
static const struct file_operations lcdev_fops = {
.owner = THIS_MODULE,
.open = lcdev_open,
.release = lcdev_release,
};
static int __init char_test_init(void)
{
int ret;
lcdev = kmalloc(sizeof(struct my_char_dev), GFP_KERNEL);
if(!lcdev) {
printk(KERN_ERR"No memory for lcdev");
ret = -ENOMEM;
goto out;
}
printk(KERN_ALERT"kmalloc ok \n");
lcdev->maj = 252;
lcdev->mio = 0;
lcdev->count = 3;
ret = register_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count, "chartest");
if(0 > ret) {
printk(KERN_ERR"register failed\n");
goto register_err;
}
printk(KERN_ALERT"register char dev ok ,ret:%d\n", ret);
lcdev->cdev = cdev_alloc();
if (!lcdev->cdev)
goto register_err;
cdev_init(lcdev->cdev, &lcdev_fops);
ret = cdev_add(lcdev->cdev,MKDEV(lcdev->maj,lcdev->mio), lcdev->count);
if(ret < 0)
goto add_fail;
printk(KERN_ALERT"maj: %d ,mio:%d\n", lcdev->maj, lcdev->mio);
return 0;
add_fail:
kobject_put(&lcdev->cdev->kobj);
register_err:
unregister_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count);
if(lcdev)
kfree(lcdev);
return -1;
out:
return ret;
}
static void __exit char_test_exit(void)
{
unregister_chrdev(lcdev->maj, CDE_NAME);
kfree(lcdev);
printk(KERN_ALERT"char test exit\n");
}
module_init(char_test_init);
module_exit(char_test_exit);
MODULE_LICENSE("GPL");
2. 验证结果
(1)驱动安装
安装成功。
(2)创建新设备节点并验证
创建了4个相同的主设备号但是次设备号分别为0、1、2、3的次设备,进行测试,前三个是可以访问的,次设备号为3的访问出错了,这是为啥?
==》 因为驱动程序中只是注册了三个子设备
3.register_chrdev_region()函数梳理
//以主设备号 = 252, 次设备号 = 0 ,count=3 为例进行分析
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count; // from = fc0 0000, to = fc0 003
dev_t n, next;
//n = fc0 0000,第二次循环 n = fc0 0003 退出循环
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0); //next = fd0 0000
if (next > to)
next = to; // next = fc0 0003
// 参数 252, 0, 3
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
从以上代码和带入参数可以得如下结论:
- for循环只执行了一次
- __register_chrdev_region()函数才是真正要干活的
(1)__register_chrdev_region() 函数
static struct char_device_struct * //252 0
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{ // 3
struct char_device_struct *cd, **cp;
int ret = 0;
int i = -1;
int count = 0;
/*分配内存*/
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary 如果传入的主设备号为0,则动态分配主设备号*/
/*动态分配的方法:从全局字符设备数组中查找成员为空下标,将其下标作为主设备号*/
if (major == 0) {
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] == NULL) {
if(!strcmp("chartest", name))
printk("i1: %d\t", i);
break;
}
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
}
/*填充刚才分配内存的指针*/
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
i = major_to_index(major); //i = 252
/*此处应该是处理添加次设备号设备的情况,for执行完成,cp处于*/
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) || //当前次设备号大于等于即将要注册的次设备号
((*cp)->baseminor + (*cp)->minorct > baseminor)))) //或者当前设备次设备号加上当前设备个数 大于即将要注册的次设备号
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
所谓的字符设备注册,就是填充了一个字符设备(struct char_device_struct)数组中的一个成员。
这里我们一次性注册了相同主设备号但不同次设备号的三个字符设备,但是字符数组成员只占用了一个。
代码添加如下打印:
mutex_lock(&chrdevs_lock);
if(!strcmp("chartest", name)) {
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] != NULL)
count += 1;
}
printk("count_1: %d\n", count);
}
....
....
mutex_unlock(&chrdevs_lock);
if(!strcmp("chartest", name)) {
count = 0;
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] != NULL)
count += 1;
}
printk("count_2: %d\n", count);
}
return cd;
结果为:
19 --》20,增加了一个数组成员。
假如相同另外一个驱动添加相同主设备号,但是不同次设备号或者相同次设备号会是什么情况?
情景一: 相同主设备号,不同次设备号
复制Demo程序,更改设备号和count 如下:
lcdev->maj = 252;
lcdev->mio = 3;
lcdev->count = 1;
ret = register_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count, "chartest");
编译注册后,可以正常访问,但是字符设备数组个数并未改变。
数组成员并未改变。
情景二: 相同的次设备号(应该会报错)
代码更改
printk(KERN_ALERT"kmalloc ok \n");
lcdev->maj = 252;
lcdev->mio = 2;
lcdev->count = 1;
ret = register_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count, "chartest");
if(0 > ret) {
运行效果:
果真报错。
4. 总结
(1)注册调用流程
register_chrdev_region()
__register_chrdev_region()
(2) 遗留问题
相同主设备号的字符设备都占用同一个字符设备数组成员,系统访问的时候是怎么区分的?