Heng30的博客
搜索 分类 关于 订阅

如何使用Linux misc设备驱动注册机制?

2025-03-13

在Linux内核中,miscdevice是一种用于简化字符设备驱动注册的机制。它适用于那些不需要主设备号的简单设备驱动。miscdevice使用一个固定的主设备号(10),开发者只需指定次设备号即可。

主要特点

  • 固定主设备号:miscdevice使用主设备号10,开发者只需关注次设备号。

  • 简化注册:通过misc_register()misc_deregister()函数,简化了设备驱动的注册和注销过程。

  • 自动创建设备节点:注册后,内核会自动在/dev目录下创建设备节点。

驱动代码

// simple.c

#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/minmax.h>
#include <linux/miscdevice.h>
#include <linux/module.h>

#define MAX_SIZE 256
static char data[MAX_SIZE];
static size_t data_len;

static int _open(struct inode *inode, struct file *file) {
    pr_info("simple_open\n");
    pr_info("device numbers: %d %d\n", imajor(inode), iminor(inode));

    if (file->f_mode & FMODE_READ) {
        pr_info("open file\n");
    }

    if (file->f_mode & FMODE_WRITE) {
        pr_info("write file\n");
    }

    return 0;
}

static int _close(struct inode *inode, struct file *file) {
    pr_info("simple_close\n");
    return 0;
}

static ssize_t _write(struct file *file, const char __user *user_buffer,
                      size_t user_len, loff_t *ppos) {
    int status;
    pr_info("simple_write\n");

    data_len = min(user_len, MAX_SIZE);
    status = copy_from_user(data, user_buffer, data_len);
    if (status) {
        pr_err("copy_from_user error\n");
        return -status;
    }

    return data_len;
}

static ssize_t _read(struct file *file, char __user *user_buffer,
                     size_t user_len, loff_t *ppos) {
    int status, len;
    pr_info("simple_read\n");

    len = min(user_len, data_len);
    status = copy_to_user(user_buffer, data, len);
    if (status) {
        pr_err("copy_to_user error\n");
        return -status;
    }

    return len;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = _read,
    .write = _write,
    .open = _open,
    .release = _close,
};

static struct miscdevice my_device = {
    .name = "testdev",
    .minor = MISC_DYNAMIC_MINOR,
    .fops = &fops,
    .mode = 0666,
};

static int __init simple_init(void) {
    int status = 0;
    pr_info("simple_init\n");

    status = misc_register(&my_device);
    if (status) {
        pr_err("misc_register error\n");
        return -status;
    }

    return 0;
}

static void __exit simple_exit(void) {
    pr_info("simple_exit\n");

    misc_deregister(&my_device);
}

module_init(simple_init);
module_exit(simple_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");

编译脚本

#!/bin/sh

top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build

obj-m += simple.o

all:
        make -C $(kernel-dir) modules M=$(top-dir)

clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)

测试

  • 编译程序:make

  • 安装驱动:insmod simple.ko

  • 查看设备文件:ls -al /dev/testdev

crw-rw-rw- 1 root root 10, 120 Mar 13 12:10 /dev/testdev
  • 写入数据:echo "hello" > /dev/testdev

  • 读取数据:head -n 1 /dev/testdev

  • 移除驱动:rmmod simple.ko