linux 字符设备创建失败

问题描述 投票:0回答:1

我写了一个这样的演示设备驱动程序:

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/slab.h>

#define CDEV_NAME "mycdev"

struct my_char_dev {
    unsigned char *ptr;
    struct semaphore sem;
    struct cdev cdev;
};

struct my_char_dev *mycdev;

int my_char_open(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "%s\n", __FUNCTION__);
    return 0;
}

int my_char_release(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "%s\n", __FUNCTION__);
    return 0;
}

ssize_t my_char_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    printk(KERN_INFO "%s\n", __FUNCTION__);
    return 0;
}

ssize_t my_char_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
    printk(KERN_INFO "%s\n", __FUNCTION__);
    return 0;
}

struct file_operations my_char_fops = {
    .owner = THIS_MODULE,
    .open = my_char_open,
    .release = my_char_release,
    .read = my_char_read,
    .write = my_char_write,
};

static int __init my_char_init(void)
{
    mycdev = kmalloc(sizeof(struct my_char_dev), GFP_KERNEL);
    if (!mycdev) {
        printk(KERN_INFO "alloc space for device failed\n");
        return -ENOMEM;
    }

    if (!alloc_chrdev_region(&mycdev->cdev.dev, 0, 1, CDEV_NAME))
        printk(KERN_INFO "%s alloc region success\n", CDEV_NAME);
    else {
        printk(KERN_INFO "%s alloc region failed\n", CDEV_NAME);
        return -ENOMEM;
    }

    cdev_init(&mycdev->cdev, &my_char_fops);
    if (!cdev_add(&mycdev->cdev, mycdev->cdev.dev, 1))
        printk(KERN_INFO "%s add device success\n", CDEV_NAME);
    else {
        printk(KERN_INFO "%s add device failed\n", CDEV_NAME);
        unregister_chrdev_region(mycdev->cdev.dev, 1);
        return -ENOMEM;
    }
    
    return 0;
}

void __exit my_char_exit(void)
{
    cdev_del(&mycdev->cdev);
    unregister_chrdev_region(mycdev->cdev.dev, 1);

    kfree(mycdev);

    printk(KERN_INFO "%s\n", __FUNCTION__);
}

module_init(my_char_init);
module_exit(my_char_exit);

MODULE_AUTHOR("xdd");
MODULE_LICENSE("GPL");

我将模块插入内核,当我猫

mycdev
时,我可以找到一个
/proc/devices
,我还通过
/dev/mycdev
成功在
mknod
下创建了一个设备节点,但是当我尝试操作
/dev/mycdev
时,我有创建后,我得到了
/dev/mycdev0: No such device or address

这就是 dmesg 喜欢的:

$ dmesg
[  235.845810] test: loading out-of-tree module taints kernel.
[  235.845836] test: module verification failed: signature and/or required key missing - tainting kernel
[  235.846238] mycdev alloc region success
[  235.846239] mycdev add device success

这是

proc/devices
中的设备:

$ cat /proc/devices | grep myc
239 mycdev

这就是我使用 mknod 的方式:

$ sudo mknod /dev/mycdev1 c 239 1

这是我创建的设备:

$ ls -l /dev/mycdev1
crw-r--r-- 1 root root 239, 1 Nov 14 00:57 /dev/mycdev1

我尝试使用

cat
来操作我创建的文件:

$ cat /dev/mycdev1
cat: /dev/mycdev1: No such device or address

当我使用

rmmod
删除内核模块时,模块删除成功,但是当我检查
/proc/devices
时,有一个
mycdev
仍然存在:

$ sudo rmmod mycdev_module
$ cat /proc/devices | grep myc
239 mycdev

我不知道为什么会发生这种情况。

我想知道为什么这个过程会失败。以及我应该做什么来纠正我的程序。

linux linux-kernel linux-device-driver
1个回答
0
投票

分配的字符设备区域的基本设备编号(

dev_t
值)通过此调用存储在
mycdev->cdev.dev
中:

    if (!alloc_chrdev_region(&mycdev->cdev.dev, 0, 1, CDEV_NAME))

之后,

mycdev->cdev.dev
将通过此调用设置为0:

    cdev_init(&mycdev->cdev, &my_char_fops);
在以下对

mycdev->cdev.dev

 的调用中,
cdev_add()
为 0:

    if (!cdev_add(&mycdev->cdev, mycdev->cdev.dev, 1))

该调用会将

mycdev->cdev.dev
设置为第二个参数的值,该值已经是其当前值 0。

尽管主设备号 0 是为“未命名设备”保留的,但它还是以某种方式成功了,但显然这不是我们想要的。

在模块退出函数中,以及模块 int 函数中的错误清理代码中,在以下对

mycdev->cdev.dev
的调用中,
unregister_chrdev_region()
仍然为 0:

    unregister_chrdev_region(mycdev->cdev.dev, 1);

这就解释了为什么

alloc_chrdev_region()
注册的字符设备区域在模块退出后仍然注册。

可以通过将调用移至

cdev_init()
,然后再调用
alloc_chrdev_region()
来解决该问题。但是,我认为最好将
dev_t
值存储在单独的变量中,以将字符设备区域的注册与
struct cdev
值的操作分离。

例如,此文件级变量可用于存储注册的字符设备编号区域的基数:

static dev_t mycdev_devt_base;

然后将

alloc_chrdev_region()
调用更改为:

    if (!alloc_chrdev_region(&mycdev_base_devt, 0, 1, CDEV_NAME))

并将

cdev_add()
调用更改为:

    if (!cdev_add(&mycdev->cdev, mycdev_base_devt, 1))

并将两个

unregister_chrdev()
调用(在模块初始化错误清理代码和模块退出函数中)更改为:

    unregister_chrdev_region(mycdev_base_devt, 1);

如果对

my_char_init()
alloc_chrdev_region()
的调用失败,
cdev_add()
中的错误清理代码会泄漏内存。在这种情况下,
mycdev
指向的已分配内存永远不会被释放。在这些情况下,该函数也会返回
-ENOMEM
,但最好返回失败的函数返回的错误号。

通常的做法是使用

goto some_label;
以与分配相反的顺序进行清理,如下所示:

static int __init my_char_init(void)
{
    int rc;

    mycdev = kmalloc(sizeof(struct my_char_dev), GFP_KERNEL);
    if (!mycdev) {
        printk(KERN_INFO "alloc space for device failed\n");
        return -ENOMEM;
    }

    rc = alloc_chrdev_region(&mycdev_base_devt, 0, 1, CDEV_NAME);
    if (rc) {
        printk(KERN_INFO "%s alloc region failed - err %d\n", CDEV_NAME, rc);
        goto fail_alloc_chrdev_region;
    }
    printk(KERN_INFO "%s alloc region success, major=%u, base minor=%u\n",
           CDEV_NAME, MAJOR(mycdev_base_devt), MINOR(mycdev_base_devt));

    cdev_init(&mycdev->cdev, &my_char_fops);
    rc = cdev_add(&mycdev->cdev, mycdev_base_devt, 1);
    if (rc) {
        printk(KERN_INFO "%s add device failed - err %d\n", CDEV_NAME, rc);
        goto fail_cdev_add;
        return rc;
    }
    printk(KERN_INFO "%s add device success\n", CDEV_NAME);

    return 0;

    /* cdev_del(&mycdev->cdev); */
fail_cdev_add:

    unregister_chrdev_region(mycdev_base_devt, 1);
fail_alloc_chrdev_region:

    kfree(mycdev);
    return rc;
}

以上版本的函数报告分配的字符设备编号区域的基本主要和次要编号。主设备号将动态分配;次设备号将为 0,由调用

alloc_chrdev_region()
的第二个参数设置。


OP 使用

mknod
命令创建一个字符特殊文件,使用
/proc/devices
文件中报告的主设备号和次设备号 1。即使驱动程序模块按预期工作,该文件也不会对应一个已注册的字符设备,因为驱动模块只保留了次设备号0。

© www.soinside.com 2019 - 2024. All rights reserved.