Ubuntu 下创建设备节点的完整驱动示例

下面是一个完整的字符设备驱动示例,它会在 /dev 目录下创建名为 mydevice 的设备节点,并实现基本的文件操作接口。

1. 驱动代码 (chardev.c)

c
 
复制
 
下载
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define DEVICE_NAME "mydevice"
#define CLASS_NAME "mymodule"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Character device driver example with /dev node");

static int major_number;
static struct class* dev_class = NULL;
static struct device* dev_device = NULL;
static struct cdev my_cdev;

// 设备缓冲区
static char *device_buffer;
static int buffer_size = 1024;
static int buffer_offset = 0;

// 文件操作函数原型
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char __user *, size_t, loff_t *);

// 文件操作结构体
static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

// 打开设备
static int device_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device opened\n");
    return 0;
}

// 关闭设备
static int device_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device closed\n");
    return 0;
}

// 读取设备
static ssize_t device_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
    int bytes_to_read;
    int bytes_read = 0;
    
    if (*offset >= buffer_offset)
        return 0;
    
    bytes_to_read = min(len, (size_t)(buffer_offset - *offset));
    
    if (copy_to_user(buf, device_buffer + *offset, bytes_to_read)) {
        return -EFAULT;
    }
    
    *offset += bytes_to_read;
    bytes_read = bytes_to_read;
    
    printk(KERN_INFO "Read %d bytes\n", bytes_read);
    return bytes_read;
}

// 写入设备
static ssize_t device_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
    int bytes_to_write;
    int bytes_written = 0;
    
    bytes_to_write = min(len, (size_t)(buffer_size - buffer_offset));
    
    if (copy_from_user(device_buffer + buffer_offset, buf, bytes_to_write)) {
        return -EFAULT;
    }
    
    buffer_offset += bytes_to_write;
    bytes_written = bytes_to_write;
    
    printk(KERN_INFO "Wrote %d bytes\n", bytes_written);
    return bytes_written;
}

// 模块初始化
static int __init chardev_init(void)
{
    dev_t dev;
    
    // 1. 动态分配设备号
    if (alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME) < 0) {
        printk(KERN_ALERT "Failed to allocate device number\n");
        return -1;
    }
    major_number = MAJOR(dev);
    
    printk(KERN_INFO "Registered with major number %d\n", major_number);
    
    // 2. 创建设备类
    dev_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(dev_class)) {
        unregister_chrdev_region(dev, 1);
        printk(KERN_ALERT "Failed to create device class\n");
        return PTR_ERR(dev_class);
    }
    
    // 3. 初始化并添加字符设备
    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;
    if (cdev_add(&my_cdev, dev, 1) < 0) {
        class_destroy(dev_class);
        unregister_chrdev_region(dev, 1);
        printk(KERN_ALERT "Failed to add character device\n");
        return -1;
    }
    
    // 4. 创建设备节点(触发udev在/dev下创建节点)
    dev_device = device_create(dev_class, NULL, dev, NULL, DEVICE_NAME);
    if (IS_ERR(dev_device)) {
        cdev_del(&my_cdev);
        class_destroy(dev_class);
        unregister_chrdev_region(dev, 1);
        printk(KERN_ALERT "Failed to create device\n");
        return PTR_ERR(dev_device);
    }
    
    // 5. 分配设备缓冲区
    device_buffer = kmalloc(buffer_size, GFP_KERNEL);
    if (!device_buffer) {
        device_destroy(dev_class, dev);
        cdev_del(&my_cdev);
        class_destroy(dev_class);
        unregister_chrdev_region(dev, 1);
        printk(KERN_ALERT "Failed to allocate device buffer\n");
        return -ENOMEM;
    }
    
    memset(device_buffer, 0, buffer_size);
    strcpy(device_buffer, "Initial device buffer content");
    buffer_offset = strlen(device_buffer);
    
    printk(KERN_INFO "Device driver loaded successfully\n");
    return 0;
}

// 模块清理
static void __exit chardev_exit(void)
{
    // 释放资源(按创建的反序)
    kfree(device_buffer);
    device_destroy(dev_class, MKDEV(major_number, 0));
    cdev_del(&my_cdev);
    class_destroy(dev_class);
    unregister_chrdev_region(MKDEV(major_number, 0), 1);
    
    printk(KERN_INFO "Device driver unloaded\n");
}

module_init(chardev_init);
module_exit(chardev_exit);

2. Makefile

makefile
 
复制
 
下载
obj-m := chardev.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

3. 编译和加载驱动

bash
 
复制
 
下载
make
sudo insmod chardev.ko

4. 验证设备节点

检查设备节点是否创建

bash
 
复制
 
下载
ls -l /dev/mydevice

正常输出类似:

text
 
复制
 
下载
crw------- 1 root root 245, 0 Jun 15 14:30 /dev/mydevice

检查sysfs接口

bash
 
复制
 
下载
ls /sys/class/mymodule/mydevice/

查看内核日志

bash
 
复制
 
下载
dmesg | tail

5. 测试设备

写入数据

bash
 
复制
 
下载
echo "Hello, Linux Driver!" | sudo tee /dev/mydevice

读取数据

bash
 
复制
 
下载
sudo cat /dev/mydevice

输出应为:

text
 
复制
 
下载
Hello, Linux Driver!

查看缓冲区内容(通过dmesg)

bash
 
复制
 
下载
dmesg | tail -n 5

6. 卸载驱动

bash
 
复制
 
下载
sudo rmmod chardev

关键点说明

  1. 设备创建流程:

    • alloc_chrdev_region() - 动态分配设备号

    • class_create() - 创建设备类

    • cdev_init() + cdev_add() - 注册字符设备操作

    • device_create() - 创建设备节点(触发udev在/dev下创建文件)

  2. 自动节点创建:

    • device_create() 的最后一个参数指定了 /dev 下的设备名称

    • 现代Linux系统通过udev自动管理/dev下的设备节点

  3. 文件操作:

    • 实现了基本的open/release/read/write操作

    • 使用copy_to_user/copy_from_user在内核和用户空间传递数据

  4. 资源管理:

    • 所有资源分配都有对应的释放操作

    • 错误处理确保资源不会泄漏

这个示例展示了Linux字符设备驱动的完整实现流程,可以直接作为更复杂驱动开发的基础模板。

 
这样的话,及生成设备文件,又生成设备类文件。设备文件和设备类文件是不是都是对设备操作的接口?他们直接又有什么不同?
 
 

在 Linux 设备驱动中,/dev 下的设备文件和 /sys/class/ 下的设备类文件确实都可以作为设备操作的接口,但它们的定位、用途和访问方式有本质区别。以下是详细对比分析:


1. /dev 设备文件

特点

  • 路径:/dev/device_name(如 /dev/mydevice

  • 本质:字符设备或块设备的用户空间接口

  • 创建方式:

    • 驱动调用 device_create() 触发 udev 自动创建

    • 或手动通过 mknod 创建

  • 访问方式:

    • 通过文件操作(open/read/write/ioctl/close

    • 使用标准 C 库函数(fopen()fread()等)或 shell 命令(catecho

典型用途

bash
 
复制
 
下载
# 写入设备
echo "data" > /dev/mydevice

# 读取设备
cat /dev/mydevice

# C 程序访问
int fd = open("/dev/mydevice", O_RDWR);
read(fd, buf, size);

优势

  • 提供字节流或块设备的抽象

  • 支持随机访问(lseek)

  • 允许多进程并发访问(依赖驱动实现)


2. /sys/class/ 设备类文件

特点

  • 路径:/sys/class/class_name/device_name/(如 /sys/class/mymodule/mydevice/

  • 本质:内核对象的属性接口(通过 sysfs 文件系统暴露)

  • 创建方式:

    • 驱动通过 class_create() 和 device_create() 自动生成

    • 属性文件通过 device_create_file() 或 DEVICE_ATTR 宏添加

  • 访问方式:

    • 只能通过单个文件读写(每个属性一个文件)

    • 通常一次读写对应一个完整属性值

典型用途

bash
 
复制
 
下载
# 查看设备状态
cat /sys/class/mymodule/mydevice/status

# 配置设备参数
echo 1 > /sys/class/mymodule/mydevice/enable

优势

  • 提供结构化属性访问

  • 支持原子操作(每个文件对应一个完整操作)

  • 更适合状态查询和配置


3. 核心区别对比

特性/dev 设备文件/sys/class/ 设备类文件
接口类型 字节流/块设备接口 属性/状态接口
访问粒度 支持任意字节读写 通常以整个属性为单位读写
功能定位 数据传输 状态监控和配置
并发控制 需驱动实现 由内核自动处理
典型操作 read/write/ioctl cat/echo 单个属性值
底层实现 通过 file_operations 通过 sysfs 属性回调
用户空间工具 dd/cat/自定义程序 shell 脚本/sysfsutils
设备号 需要主次设备号 不需要

4. 何时使用哪种接口?

优先使用 /dev 的情况

  • 需要传输原始数据流(如传感器数据、网络包)

  • 实现设备文件的标准操作(如串口设备的 read/write

  • 需要随机访问或复杂控制(ioctl

优先使用 /sys/class/ 的情况

  • 暴露设备状态信息(如温度、电压)

  • 提供开关/模式配置(如启用/禁用功能)

  • 需要结构化属性(如 brightnesstrigger


5. 实际驱动中的联合使用

现代 Linux 驱动通常同时提供两种接口:

c
 
复制
 
下载
// 1. 标准设备操作(通过 /dev)
static struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

// 2. sysfs 属性(通过 /sys/class/)
static DEVICE_ATTR(value, 0644, show_value, store_value);

典型场景:

  • 通过 /dev/mydevice 传输数据

  • 通过 /sys/class/mymodule/mydevice/enable 控制设备开关


6. 用户空间访问示例对比

通过 /dev 写入数据

c
 
复制
 
下载
int fd = open("/dev/mydevice", O_RDWR);
write(fd, "Hello", 5);
close(fd);

通过 /sys/class 配置参数

c
 
复制
 
下载
int fd = open("/sys/class/mymodule/mydevice/enable", O_WRONLY);
write(fd, "1", 1);
close(fd);

总结

  • /dev 是传统的 Unix 设备抽象,适合数据流传输

  • /sys/class 是 Linux 设备模型的核心,适合状态管理

  • 设计驱动时:

    • 需要数据传输 → 实现 /dev 接口

    • 需要配置/状态 → 实现 /sys/class 属性

    • 两者可互补共存

    •  
 
 
posted @ 2025-06-14 10:07  MaxBruce  阅读(22)  评论(0)    收藏  举报