将简易字符设备驱动集成到Linux源码中

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

一、环境选择

虚拟机软件:VMware Workstation 17.6.3

操作系统:Ubuntu20.04

交叉编译工具链:gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)

QEMU版本:QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.30)

Linux源码版本:6.13.6

二、代码实现

字符设备驱动程序 

这段代码是一个Linux 字符设备驱动程序,主要功能是实现内核空间与用户空间的双向数据通信。以下是对代码的具体说明:

  1. 驱动类型

    • 属于字符设备驱动(Character Device Driver),遵循 Linux 内核驱动模型,通过文件操作接口(open/read/write等)与用户空间交互。
  2. 核心功能

    • 动态分配设备号并注册字符设备
    • 实现设备文件的打开、关闭、读取、写入操作
    • 通过设备类机制自动创建设备节点,避免手动执行mknod
    • 利用copy_to_user/copy_from_user实现内核与用户空间的数据安全传输

hello_drv.c代码实现:

#include <linux/module.h>
#include <linux/init.h>         
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DEV_NAME "hello_drv"
#define BUFFER_SIZE 256

static dev_t dev_num;
static struct cdev hello_cdev;
static char kernel_buffer[BUFFER_SIZE] = "Hello from the kernel module!\n";
static struct class *hello_class;
static struct device *hello_device;
// 设备打开操作
static int hello_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "[打开设备] Hello: Device opened\n");
    return 0;
}

// 设备释放操作
static int hello_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "[关闭设备] Hello: Device closed\n");
    return 0;
}

// 设备读取操作
static ssize_t hello_read(struct file *file, char __user *buf, size_t count, loff_t *loff) {
    size_t len = strlen(kernel_buffer);
    size_t read_len = min(count, len);
    if (copy_to_user(buf, kernel_buffer, read_len))
        return -EFAULT;
    printk(KERN_INFO "[读取操作] Hello: Sent %zu characters to the user\n", read_len);
    return read_len;
}

// 设备写入操作
static ssize_t hello_write(struct file *file, const char __user *buf, size_t count, loff_t *loff) {
    size_t write_len = min(count, BUFFER_SIZE - 1);
    if (copy_from_user(kernel_buffer, buf, write_len))
        return -EFAULT;
    kernel_buffer[write_len] = '\0';
    printk(KERN_INFO "[写入操作] Hello: Received %zu characters from the user: %s\n", 
           write_len, kernel_buffer);
    return write_len;
}

// 设备操作函数表
static const struct file_operations hello_fops = {
    .open = hello_open,
    .release = hello_release,
    .read = hello_read,
    .write = hello_write,
};

// 模块初始化
static int __init hello_init(void) {
    int ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }
    printk(KERN_INFO "[加载模块] Hello: Module loaded successfully! Major number: %d\n", MAJOR(dev_num));
    
    cdev_init(&hello_cdev, &hello_fops);
    hello_cdev.owner = THIS_MODULE;
    ret = cdev_add(&hello_cdev, dev_num, 1);
    if (ret < 0) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to add cdev\n");
        return ret;
    }
    // 创建设备类(用于自动创建设备节点)
    hello_class = class_create("hello_class");
    if (IS_ERR(hello_class)) {
        cdev_del(&hello_cdev);
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to create class\n");
        return PTR_ERR(hello_class);
    }
    
    // 创建设备节点(自动在/dev下生成hello_drv)
    hello_device = device_create(hello_class, NULL, dev_num, NULL, "hello_drv");
    if (IS_ERR(hello_device)) {
        class_destroy(hello_class);
        cdev_del(&hello_cdev);
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to create device\n");
        return PTR_ERR(hello_device);
    }
     printk(KERN_INFO "[加载模块] Hello: Create device file with: mknod /dev/hello_drv c %d 0\n", 
MAJOR(dev_num));
    return 0;
}

// 模块卸载
static void __exit hello_exit(void) {
     // 先释放设备和类
    if (hello_device)
        device_destroy(hello_class, dev_num);
    if (hello_class)
        class_destroy(hello_class);
    cdev_del(&hello_cdev);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "[卸载模块] Hello: Module unloaded successfully!\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

在drivers目录下char目录创建hello_drv目录,同时touch创建hello_drv.c和Kconfig和Makefile编写Kconfig和Makefile如图所示 

同时修改divers/char目录下的Makefile文件,在文件末尾添加

将模块静态编译进内核,内核启动时自动加载,随内核关闭而卸载。

obj-y                          +=hello_drv/

在Kconfig文件末尾添加

source "drivers/char/hello_drv/Kconfig"

配置环境

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

 编译内核

make -j8 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

用户空间测试代码

hello_test.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define DEV_PATH "/dev/hello_drv"  // 与驱动中的DEV_NAME保持一致
#define BUFFER_SIZE 256

int main() {
    int fd;
    char buffer[BUFFER_SIZE];

    // 打开设备
    fd = open(DEV_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }

    // 读取初始数据
    memset(buffer, 0, BUFFER_SIZE);
    if (read(fd, buffer, BUFFER_SIZE) < 0) {
        perror("Failed to read from device");
        close(fd);
        return -1;
    }
    printf("[用户程序] 读取数据: %s", buffer);

    // 写入数据
    const char* write_data = "Hello from user space!\n";
    if (write(fd, write_data, strlen(write_data)) < 0) {
        perror("Failed to write to device");
        close(fd);
        return -1;
    }
    printf("[用户程序] 写入数据: %s", write_data);

    // 再次读取数据
    memset(buffer, 0, BUFFER_SIZE);
    if (read(fd, buffer, BUFFER_SIZE) < 0) {
        perror("Failed to read from device");
        close(fd);
        return -1;
    }
    printf("[用户程序] 读取更新后数据: %s", buffer);

    // 关闭设备
    close(fd);
    return 0;
}

编译测试程序

aarch64-linux-gnu-gcc -static -o hello_test hello_test.c

 复制到共享文件夹sharedir

cp hello_test ../../sharedir

三、用户空间测试

启动qemu模拟器

qemu-system-aarch64 \
-M virt \
-cpu cortex-a53 \
-kernel arch/arm64/boot/Image.gz \
-initrd initramfs_arm64.img \
-nographic \
-serial mon:stdio \
-append "console=ttyAMA0" \
-fsdev local,security_model=passthrough,id=fsdev0,path=sharedir \
-device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare

挂载共享目录,可以看到用户空间测试程序

 通过日志输出可以看到,字符设备驱动已经自动加载了

 现在运行用户空间测试程序,可以看到输出结果符合预期

通信的本质其实就是看见同一份资源, 用户空间的 fd 与内核的 file 结构体通过文件系统层建立映射。

内核驱动通过class_createdevice_create自动创建设备节点(如 /dev/hello_drv),用户空间通过访问该节点与内核通信。

内核驱动实现 open/read/write/release 等文件操作函数,用户空间通过系统调用 read(fd, buf, len))触发内核逻辑。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值