Linux char 设备创建失败

linux char device create failed

提问人:Arvin 提问时间:11/13/2023 最后编辑:Arvin 更新时间:11/16/2023 访问量:53

问:

我编写了一个演示设备驱动程序,如下所示:

#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");

我在内核中插入模块,当我cat时,我可以找到一个,我也成功地在下做了一个设备节点,但是当我尝试操作我创建的模块时,我得到了.mycdev/proc/devices/dev/mycdevmknod/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

这就是我使用 mknid 的方式:

$ 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/devicesmycdev

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

我不知道为什么会这样。

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

linux-device-driver

评论

1赞 0andriy 11/14/2023
你在这里和那里有很多:s,你不会与我们分享日志吗?如果这么秘密,为什么还要费心去问呢?另外,不清楚您在操作 /dev/mydev 下是什么意思......printk()
1赞 Ian Abbott 11/14/2023
您将错误的次要设备编号传递给 。它应该是,而不是。mknod01
1赞 Ian Abbott 11/14/2023
一般来说,我建议返回 和 返回的实际错误代码,而不是将它们替换为 .此外,如果其中任一函数失败,代码应释放 指向的内存。alloc_chrdev_region()cdev_add()-ENOMEMmycdev
1赞 Ian Abbott 11/15/2023
cdev_init()将 清零,这将丢失 存储的值。您可以将呼叫移动到呼叫之前发生,以阻止这种情况发生。struct cdevdevalloc_chrdev_region()cdev_init()alloc_chrdev_region()
1赞 Ian Abbott 11/15/2023
...不过,最好将设置的值存储在不同的变量中,因为这为您提供了有关如何支持多个设备的更多选项 - 您可以对所有设备使用单个设备,也可以为每个设备使用嵌入的单个设备。devalloc_chrdev_region()struct cdevstruct cdev

答:

0赞 Ian Abbott 11/15/2023 #1

此调用存储已分配字符设备区域的基本设备编号(值):dev_tmycdev->cdev.dev

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

之后,将通过以下调用设置为 0:mycdev->cdev.dev

    cdev_init(&mycdev->cdev, &my_char_fops);

mycdev->cdev.dev在以下调用中为 0:cdev_add()

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

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

即使主要设备编号 0 是为“未命名设备”保留的,这在某种程度上还是成功的,但显然这不是预期的。

在模块退出函数中,以及在模块 int 函数中的错误清理代码中,在以下调用中仍为 0:mycdev->cdev.devunregister_chrdev_region()

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

这就解释了为什么在模块退出后,注册的字符设备区域仍会注册。alloc_chrdev_region()

可以通过将调用移至调用之前来解决此问题。但是,我认为最好将值存储在单独的变量中,以将字符设备区域的注册与值的操作分离。cdev_init()alloc_chrdev_region()dev_tstruct 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);

如果对 or 的任一调用失败,则中的错误清理代码会泄漏内存。在这些情况下,所指向的分配内存永远不会被释放。在这些情况下,该函数也会返回,但最好返回失败的函数返回的错误号。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 使用该命令使用文件中报告的主要设备编号和次要设备编号 1 创建字符专用文件。即使驱动程序模块按预期工作,此文件也不会与已注册字符设备相对应,因为驱动程序模块仅保留次要设备编号 0。mknod/proc/devices

评论

0赞 0andriy 11/15/2023
您可以启用语法突出显示吗?