如何在Linux内核模块的init_module代码中创建一个设备节点?

51

我正在为Linux内核编写一个模块,我想在init()函数中创建一些设备节点:

int init_module(void)
{
    Major = register_chrdev(0, DEVICE_NAME, &fops);

    // Now I want to create device nodes
    // with the returned major number
}

我还希望内核为我的第一个节点分配一个次设备号,然后我自己分配其他节点的次设备号。

在代码中,我该如何实现这一点?我不想使用 mknod() 在 shell 中创建设备。

3个回答

80
为了更好地控制设备号和设备的创建,您可以按照以下步骤执行(而不是使用register_chrdev()):
  1. 调用alloc_chrdev_region()来获取一个主设备号和一系列次设备号。
  2. 使用class_create()为您的设备创建一个设备类。
  3. 对于每个设备,调用cdev_init()cdev_add()将字符设备添加到系统中。
  4. 对于每个设备,调用device_create()。这样,Udev会为您的设备创建设备节点。无需使用mknod()等命令。device_create()还允许您控制设备的名称。
在互联网上可能有很多这方面的例子,这里就是其中之一

2
抱歉翻出这个过去的问题,但是如果许可证不是GPL,是否有相应的方法来完成这个操作?class_create不能与非GPL许可证一起使用。 - Piotr
1
@Piotr:实际上,我不知道它是否存在。 - Eugene
3
执行以下清理操作: device_destroy(它也负责删除设备节点),cdev_del 从内核中注销设备。在删除每个设备之后,调用 class_destroy 删除类,然后 - 调用 unregister_chrdev_region。创建设备时所做的操作按通常的顺序相反地撤消。 - Eugene
1
我认为应该在cdev_add()之后调用device_create()cdev_add()准备内核结构以维护设备,而device_create()使设备可用于用户空间等其他操作。一旦您将设备提供给用户空间,该设备就应该准备好处理来自那里的请求。 - Eugene
抱歉挖出这个话题,但是我有一个问题。设备文件被创建之后,我想要对其进行读取、写入等操作。为此,每次加载模块时都必须使用 chmod 更改文件权限。有没有自动化的方法来完成这个过程? - Parth K
显示剩余6条评论

15
static int __init ofcd_init(void) /* Constructor */
{
    printk(KERN_INFO "Welcome!");
    if (alloc_chrdev_region(&first, 0, 1, "char_dev") < 0)    //$cat /proc/devices
    {
        return -1;
    }
    if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)    //$ls /sys/class
    {
        unregister_chrdev_region(first, 1);
        return -1;
    }
    if (device_create(cl, NULL, first, NULL, "mynull") == NULL)    //$ls /dev/
    {
        class_destroy(cl);
        unregister_chrdev_region(first, 1);
        return -1;
    }
    cdev_init(&c_dev, &fops);
    if (cdev_add(&c_dev, first, 1) == -1)
    {
        device_destroy(cl, first);
        class_destroy(cl);
        unregister_chrdev_region(first, 1);
        return -1;
    }
    return 0;
}

1
也许应该在调用device_create()之前先调用cdev_add()。这似乎是更常见的顺序。 - Craig McQueen
1
如果您像内核一样使用goto,代码可能会更清晰。 - Jagdish
您的清理程序缺少 cdev_del。请参考:https://dev59.com/TG025IYBdhLWcg3wfmLw#45531867 - Ciro Santilli OurBigBook.com

9

最小可运行示例

从其他答案中最小化。使用GitHub upstream进行测试设置。

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h> /* register_chrdev, unregister_chrdev */
#include <linux/module.h>
#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */

#define NAME "lkmc_character_device_create"

static int major = -1;
static struct cdev mycdev;
static struct class *myclass = NULL;

static int show(struct seq_file *m, void *v)
{
    seq_printf(m, "abcd");
    return 0;
}

static int open(struct inode *inode, struct file *file)
{
    return single_open(file, show, NULL);
}

static const struct file_operations fops = {
    .llseek = seq_lseek,
    .open = open,
    .owner = THIS_MODULE,
    .read = seq_read,
    .release = single_release,
};

static void cleanup(int device_created)
{
    if (device_created) {
        device_destroy(myclass, major);
        cdev_del(&mycdev);
    }
    if (myclass)
        class_destroy(myclass);
    if (major != -1)
        unregister_chrdev_region(major, 1);
}

static int myinit(void)
{
    int device_created = 0;

    /* cat /proc/devices */
    if (alloc_chrdev_region(&major, 0, 1, NAME "_proc") < 0)
        goto error;
    /* ls /sys/class */
    if ((myclass = class_create(THIS_MODULE, NAME "_sys")) == NULL)
        goto error;
    /* ls /dev/ */
    if (device_create(myclass, NULL, major, NULL, NAME "_dev") == NULL)
        goto error;
    device_created = 1;
    cdev_init(&mycdev, &fops);
    if (cdev_add(&mycdev, major, 1) == -1)
        goto error;
    return 0;
error:
    cleanup(device_created);
    return -1;
}

static void myexit(void)
{
    cleanup(1);
}

module_init(myinit)
module_exit(myexit)
MODULE_LICENSE("GPL");

1
Santilli - +1 非常棒的示例。 - Guy Avraham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接