当前位置:   article > 正文

字符设备驱动开发基础—从驱动中进行读写操作_驱动读写操作的流程

驱动读写操作的流程

往期回顾:

字符设备驱动开发基础—静态/动态注册设备号,使用cdev注册驱动-CSDN博客

字符设备驱动基础—sys文件系统,udev介绍,驱动模块在内核空间注册设备-CSDN博客

本文主要介绍的是 如何通过驱动进行设备的读写操作

内核空间与用户空间进行数据交互

内核空间操作系统内核运行的空间,拥有最高的权限,可以直接访问硬件资源和管理系统资源。

用户空间:用户应用程序运行的空间,权限受限,无法直接访问硬件资源,必须通过系统调用与内核交互。

交互方式:

  • 系统调用(System Call):用户空间的应用程序通过系统调用请求内核执行某些操作,比如文件操作、进程管理、内存分配等。系统调用是用户空间进入内核空间的主要途径。
    • 例子:open(), read(), write(), fork()

系统调用

  • 定义:系统调用是用户程序与操作系统内核进行交互的接口,通过系统调用,用户程序可以请求内核执行特定的服务。
  • 过程:
    1. 用户程序发起系统调用请求。
    2. 处理器从用户模式切换到内核模式。
    3. 内核执行相应的服务程序。
    4. 内核模式切换回用户模式,返回结果给用户程序。

调用框架图:

注:本文只是探讨数据交互过程,没有涉及到操作寄存器,控制硬件,只是涉及到用户空间与内核空间的数据交互

在这里插入图片描述

驱动示例代码

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

#define DEVICE_NAME "my_char_device"

#define BUF_LEN 80
static char message[BUF_LEN];
static short message_len;


static int major_number;
static struct class *my_class = NULL;
static struct device *my_device = NULL;
static struct cdev mydev; // 声明 cdev 结构体

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 *file, char __user *user_buffer, size_t len, loff_t *offset) {
    int bytes_to_copy = message_len - *offset;
    if (bytes_to_copy < 0) bytes_to_copy = 0;
    if (bytes_to_copy > len) bytes_to_copy = len;
    if (bytes_to_copy == 0) return 0;

    if (copy_to_user(user_buffer, message + *offset, bytes_to_copy) != 0) {
        return -EFAULT;
    }

    *offset += bytes_to_copy;
    printk(KERN_INFO "Sent %d characters to the user\n", bytes_to_copy);
    return bytes_to_copy;
}

static ssize_t device_write(struct file *file, const char __user *user_buffer, size_t len, loff_t *offset) {
    int bytes_to_copy = len;
    if (bytes_to_copy > BUF_LEN - 1) bytes_to_copy = BUF_LEN - 1;

    if (copy_from_user(message, user_buffer, bytes_to_copy) != 0) {
        return -EFAULT;
    }

    message[bytes_to_copy] = '\0';
    message_len = bytes_to_copy;
    printk(KERN_INFO "Received %d characters from the user\n", bytes_to_copy);
    return bytes_to_copy;
}

static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};


static int __init test_init(void)
{
    int retval;
    dev_t dev;

    printk(KERN_INFO "module init success\n");

    // 1. 动态分配主次设备号
    retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to allocate major number\n");
        goto fail_alloc_chrdev_region;
    }

    major_number = MAJOR(dev);
    printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));

    // 2. 初始化 cdev 结构体并添加到内核
    cdev_init(&mydev, &fops);
    retval = cdev_add(&mydev, dev, 1);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to add cdev\n");
        goto fail_cdev_add;
    }

    // 3. 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class))
    {
        printk(KERN_ERR "Failed to create class\n");
        retval = PTR_ERR(my_class);
        goto fail_class_create;
    }

    // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!
    my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);
    if (IS_ERR(my_device))
    {
        printk(KERN_ERR "Failed to create device\n");
        retval = PTR_ERR(my_device);
        goto fail_device_create;
    }

    printk(KERN_INFO "my_char_device: module loaded\n");
    return 0;

fail_device_create:
    class_destroy(my_class);
fail_class_create:
    cdev_del(&mydev);
fail_cdev_add:
    unregister_chrdev_region(dev, 1);
fail_alloc_chrdev_region:
    return retval;
}

static void __exit test_exit(void)
{
    dev_t dev = MKDEV(major_number, 0);
    if (my_device)
        device_destroy(my_class, dev);
    if (my_class)
        class_destroy(my_class);
    cdev_del(&mydev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "my_char_device: module unloaded\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141

关键解析

Linux理念:一切皆为文件,因此设备也是一种文件,也可以直接进行读写操作。

device_opendevice_release:设备打开和关闭时的处理函数。

device_read:从设备读取数据并复制到用户空间。

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 *file, char __user *user_buffer, size_t len, loff_t *offset) {
    int bytes_to_copy = message_len - *offset;
    if (bytes_to_copy < 0) bytes_to_copy = 0;
    if (bytes_to_copy > len) bytes_to_copy = len;
    if (bytes_to_copy == 0) return 0;

    if (copy_to_user(user_buffer, message + *offset, bytes_to_copy) != 0) {
        return -EFAULT;
    }

    *offset += bytes_to_copy;
    printk(KERN_INFO "Sent %d characters to the user\n", bytes_to_copy);
    return bytes_to_copy;
}

static ssize_t device_write(struct file *file, const char __user *user_buffer, size_t len, loff_t *offset) {
    int bytes_to_copy = len;
    if (bytes_to_copy > BUF_LEN - 1) bytes_to_copy = BUF_LEN - 1;

    if (copy_from_user(message, user_buffer, bytes_to_copy) != 0) {
        return -EFAULT;
    }

    message[bytes_to_copy] = '\0';
    message_len = bytes_to_copy;
    printk(KERN_INFO "Received %d characters from the user\n", bytes_to_copy);
    return bytes_to_copy;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

文件操作结构体

定义文件操作结构体,将文件操作函数指针绑定到对应的操作函数。

static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

本文重点使用的函数:copy_to_usercopy_from_user

copy_to_user 用于将数据从内核空间复制到用户空间。函数原型如下:

int copy_to_user(void __user *to, const void *from, unsigned long n);
  • 1
  • to:目标用户空间地址。
  • from:源内核空间地址。
  • n:要复制的字节数。

如果复制成功,返回 0;如果失败,返回未能复制的字节数。

copy_from_user 用于将数据从用户空间复制到内核空间。函数原型如下:

int copy_from_user(void *to, const void __user *from, unsigned long n);
  • 1
  • to:目标内核空间地址。
  • from:源用户空间地址。
  • n:要复制的字节数。

如果复制成功,返回 0;如果失败,返回未能复制的字节数。

用户程序示例代码

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

int main(int argc, char *argv[]) {
    int fd;
    char buf[80] = {0};
    
    // 检查参数数量
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <r|w> [data_to_write]\n", argv[0]);
        return -1;
    }

    // 打开设备节点
    fd = open("/dev/my_char_device", O_RDWR);
    if (fd < 0) {
        perror("open error");
        return -1;
    }

    if (strcmp(argv[1], "w") == 0) {
        // 写操作
        if (argc < 3) {
            fprintf(stderr, "Please provide data to write.\n");
            close(fd);
            return -1;
        }
        strncpy(buf, argv[2], sizeof(buf) - 1);
        if (write(fd, buf, sizeof(buf)) < 0) {
            perror("write error");
            close(fd);
            return -1;
        }
        printf("Data written: %s\n", buf);
    } else if (strcmp(argv[1], "r") == 0) {
        // 读操作
        if (read(fd, buf, sizeof(buf)) < 0) {
            perror("read error");
            close(fd);
            return -1;
        }
        printf("Data read: %s\n", buf);
    } else {
        fprintf(stderr, "Invalid operation. Use 'r' for read or 'w' for write.\n");
        close(fd);
        return -1;
    }

    // 关闭设备节点
    close(fd);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

实现了简单对设备的读写测试, 主要是调用了 系统调用 的 open read write 接口

实现效果

加载编译后的模块到内核,在dev目录生成了 my_char_device 设备文件

在这里插入图片描述

运行编译好的用户程序

在这里插入图片描述

同样的也可以直接使用cat 和echo命令

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/938109
推荐阅读
相关标签
  

闽ICP备14008679号