Linux字符设备创建失败

drnojrws  于 2023-11-17  发布在  Linux
关注(0)|答案(1)|浏览(119)

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

#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 /proc/devices时,我可以找到一个mycdev,我也通过mknod成功地在/dev/mycdev下创建了一个设备节点,但是当我试图操作我创建的/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


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

np8igboo

np8igboo1#

分配的角色设备区域的基本设备号(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);


在下面对cdev_add()的调用中,mycdev->cdev.dev为0:

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


该调用将mycdev->cdev.dev设置为第二个参数的值,该值已经是其当前值0。
尽管主要设备号0是为“未命名设备”保留的,但这在某种程度上成功了,但显然这不是预期的。
在模块exit函数中,以及在模块int函数中的错误清理代码中,在以下对unregister_chrdev_region()的调用中,mycdev->cdev.dev仍然为0:

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


这解释了为什么alloc_chrdev_region()注册的角色设备区域在模块退出后仍然注册。
这个问题可以通过在调用alloc_chrdev_region()之前移动对cdev_init()的调用来解决。但是,我认为最好将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()调用(在模块init错误清理代码和模块exit函数中)更改为:

unregister_chrdev_region(mycdev_base_devt, 1);


如果对alloc_chrdev_region()cdev_add()的调用失败,my_char_init()中的错误清理代码会泄漏内存。在这些情况下,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。

相关问题