GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

Linux字符设备驱动开发

下面是一个Linux字符设备驱动开发的完整示例,以控制一个简单的LED设备为例。这个示例涵盖了驱动开发的核心流程:初始化、文件操作接口实现、用户空间交互等,适合理解驱动开发的基本框架。


示例:LED字符设备驱动 (Linux Kernel Module)

功能描述

  • 通过驱动控制一个GPIO连接的LED。

  • 用户空间通过 /dev/led_dev 设备文件控制LED:

    • 写入 '1' 点亮LED

    • 写入 '0' 熄灭LED

    • 读取设备返回当前LED状态。


代码实现

1. 驱动源码 (led_driver.c)

c
 
复制
 
下载
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>     // GPIO控制头文件
#include <linux/errno.h>

#define DEVICE_NAME "led_dev"
#define LED_GPIO 21         // 假设LED连接在GPIO21

static dev_t dev_num;
static struct cdev led_cdev;
static struct class *led_class;

// 文件操作函数
static int led_open(struct inode *inode, struct file *file) {
    return 0;
}

static ssize_t led_read(struct file *file, char __user *buf, size_t len, loff_t *offset) {
    char state;
    state = gpio_get_value(LED_GPIO) ? '1' : '0'; // 读取GPIO电平
    if (copy_to_user(buf, &state, 1))
        return -EFAULT;
    return 1;
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t len, loff_t *offset) {
    char val;
    if (copy_from_user(&val, buf, 1))
        return -EFAULT;

    if (val == '1')
        gpio_set_value(LED_GPIO, 1); // 点亮LED
    else if (val == '0')
        gpio_set_value(LED_GPIO, 0); // 熄灭LED
    else
        return -EINVAL;

    return 1;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
};

// 模块初始化
static int __init led_init(void) {
    // 1. 申请设备号
    alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    
    // 2. 注册字符设备
    cdev_init(&led_cdev, &fops);
    cdev_add(&led_cdev, dev_num, 1);
    
    // 3. 创建设备节点 (/dev/led_dev)
    led_class = class_create(THIS_MODULE, "led_class");
    device_create(led_class, NULL, dev_num, NULL, DEVICE_NAME);
    
    // 4. 初始化GPIO
    if (gpio_request(LED_GPIO, "led_gpio")) {
        printk(KERN_ERR "Failed to request GPIO %d\n", LED_GPIO);
        return -EBUSY;
    }
    gpio_direction_output(LED_GPIO, 0); // 初始化为输出模式,默认低电平(熄灭)
    
    printk(KERN_INFO "LED driver loaded!\n");
    return 0;
}

// 模块退出
static void __exit led_exit(void) {
    // 1. 释放GPIO
    gpio_set_value(LED_GPIO, 0);
    gpio_free(LED_GPIO);
    
    // 2. 销毁设备节点和类
    device_destroy(led_class, dev_num);
    class_destroy(led_class);
    
    // 3. 注销字符设备
    cdev_del(&led_cdev);
    unregister_chrdev_region(dev_num, 1);
    
    printk(KERN_INFO "LED driver unloaded!\n");
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple LED Driver");

2. 编译驱动 (Makefile)

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

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

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

3. 用户空间测试步骤

bash
 
复制
 
下载
# 编译并加载驱动
$ make
$ sudo insmod led_driver.ko

# 查看设备节点(自动创建在 /dev/led_dev)
$ ls -l /dev/led_dev

# 控制LED
$ echo '1' | sudo tee /dev/led_dev  # 点亮
$ echo '0' | sudo tee /dev/led_dev  # 熄灭

# 读取LED状态
$ cat /dev/led_dev  # 输出 '1' 或 '0'

# 卸载驱动
$ sudo rmmod led_driver

关键驱动开发概念解析

  1. 设备号管理

    • alloc_chrdev_region:动态申请主设备号。

    • dev_t:包含主/次设备号的结构体。

  2. 字符设备注册

    • cdev_init:初始化cdev结构体,关联文件操作函数集fops

    • cdev_add:将设备添加到内核。

  3. 用户空间接口

    • 实现file_operations中的关键函数:

      • .read:用户调用read()时触发。

      • .write:用户调用write()时触发。

      • .open:设备打开时调用。

  4. 硬件操作

    • gpio_request:申请GPIO资源。

    • gpio_direction_output:配置GPIO为输出模式。

    • gpio_set_value:设置 GPIO 电平。

  5. 自动创建设备节点

    • class_create:创建设备类(出现在/sys/class/)。

    • device_create:在/dev/下创建设备文件。


扩展建议

  1. 添加中断支持:实现按键中断处理。

  2. 支持设备树(DTS):将GPIO编号移至设备树,增强可移植性。

  3. 实现IOCTL:通过ioctl()支持更多控制命令(如调整亮度)。

  4. 同步与锁:添加mutex防止并发冲突。

注意事项:实际硬件操作需根据开发板的GPIO设计调整(如树莓派、BeagleBone等),并确保内核已启用CONFIG_GPIOLIB选项。

这个示例覆盖了驱动开发的核心流程,通过修改硬件操作部分(如替换GPIO操作为I2C/SPI接口),即可扩展为更复杂的设备驱动。

posted on 2025-06-16 07:44  GKLBB  阅读(96)  评论(0)    收藏  举报