2025-02-27
每次安装字符设备驱动都需要使用mknod命令,在/dev目录下创建对应的字符设备。这显然是一件麻烦的事情。幸亏Linux内核为我们提供了应对方法。可以通过函数在/dev目录下创建对应的字符设备文件。
// simple_cdev.c
#include <linux/device.h>
#include <linux/device/class.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#define BUFFER_SIZE 1024
#define DEV_NAME "simple_cdev"
typedef struct {
    int major;                // 主设备号
    int minor;                // 从设备号
    char buffer[BUFFER_SIZE]; // 缓冲区
    struct class *class;      // 设备类
    struct device *dev;       // /dev目录下的设备
} simple_cdev_t;
static simple_cdev_t my_cdev = {0};
static int _open(struct inode *inode, struct file *file) {
    pr_info("simple_cdev_open\n");
    return 0;
}
static ssize_t _read(struct file *file, char __user *userbuf, size_t size,
                     loff_t *offset) {
    pr_info("simple_cdev_read\n");
    size = min(size, BUFFER_SIZE);
    unsigned long remain_len = copy_to_user(userbuf, my_cdev.buffer, size);
    if (remain_len > 0) {
        pr_err("copy_to_user failed\n");
        return -EIO;
    }
    return size;
}
static ssize_t _write(struct file *file, const char __user *userbuf,
                      size_t size, loff_t *offset) {
    pr_info("simple_cdev_write\n");
    size = min(size, BUFFER_SIZE);
    unsigned long remain_len = copy_from_user(my_cdev.buffer, userbuf, size);
    if (remain_len > 0) {
        pr_err("copy_from_user failed\n");
        return -EIO;
    }
    return size;
}
static int _release(struct inode *inode, struct file *file) {
    pr_info("simple_cdev_release\n");
    return 0;
}
static struct file_operations fops = {
    .open = _open,
    .read = _read,
    .write = _write,
    .release = _release,
};
static int __init simple_cdev_init(void) {
    pr_info("simple_cdev_init\n");
    my_cdev.minor = 0;
    // 动态申请主设备号
    my_cdev.major = register_chrdev(0, DEV_NAME, &fops);
    if (my_cdev.major < 0) {
        pr_err("simple_cdev register_chrdev failed!\n");
        return -EIO;
    }
    pr_info("simple_cdev major = %d, minor = %d\n", my_cdev.major,
            my_cdev.minor);
    // 申请设备类, 会在/sys/class目录下常见一个mycdev的目录
    my_cdev.class = class_create("mycdev");
    if (IS_ERR(my_cdev.class)) {
        pr_err("simple_cdev class_create failed\n");
        return PTR_ERR(my_cdev.class);
    }
    // 申请设备对象,会在/dev目录下创建设备文件simple_cdev,
    // 并且在/sys/class/mycdev目录下创建一个simple_cdev的设备节点
    my_cdev.dev =
        device_create(my_cdev.class, NULL, MKDEV(my_cdev.major, my_cdev.minor),
                      NULL, DEV_NAME);
    if (IS_ERR(my_cdev.dev)) {
        pr_err("simple_cdev device_create failed\n");
        return PTR_ERR(my_cdev.dev);
    }
    return 0;
}
static void __exit simple_cdev_exit(void) {
    pr_info("simple_cdev_exit\n");
    unregister_chrdev(my_cdev.major, DEV_NAME);
    device_destroy(my_cdev.class, MKDEV(my_cdev.major, my_cdev.minor));
    class_destroy(my_cdev.class);
}
module_init(simple_cdev_init);
module_exit(simple_cdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple_cdev kernel module");
#!/bin/sh
top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build
obj-m += simple_cdev.o
all:
        make -C $(kernel-dir) modules M=$(top-dir)
clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers
        make -C $(kernel-dir) clean m=$(top-dir)
安装驱动:insmod simple_cdev.ko
查看simple_cdev设备文件:ls -al /dev/simple_cdev
crw------- 1 root root 240, 0 Feb 27 01:00 /dev/simple_cdev
echo "hello" > /dev/simple_cdev[69730.857162] simple_cdev_open
[69730.857197] simple_cdev_write
[69730.857202] simple_cdev_release
head -n 1 /dev/simple_cdev[69787.848537] simple_cdev_open
[69787.848574] simple_cdev_read
[69787.848621] simple_cdev_release
rmmod simple_cdev.ko