字符设备驱动开发基础—从驱动中进行读写操作
创始人
2024-11-13 16:07:52
0

往期回顾:

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

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

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

文章目录

    • 内核空间与用户空间进行数据交互
    • 驱动示例代码
      • 关键解析
    • 用户程序示例代码
    • 实现效果

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

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

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

交互方式:

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

系统调用

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

调用框架图:

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

在这里插入图片描述

驱动示例代码

#include  #include  #include  #include  #include  #include   #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");  

关键解析

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; }  

文件操作结构体

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

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

本文重点使用的函数:copy_to_usercopy_from_user

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

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

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

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

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

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

用户程序示例代码

#include  #include  #include  #include  #include  #include   int main(int argc, char *argv[]) {     int fd;     char buf[80] = {0};          // 检查参数数量     if (argc < 2) {         fprintf(stderr, "Usage: %s  [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; } 

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

实现效果

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

在这里插入图片描述

运行编译好的用户程序

在这里插入图片描述

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

在这里插入图片描述

相关内容

热门资讯

攻略辅助!德州私人局怎么透视,... 攻略辅助!德州私人局怎么透视,wejoker辅助软件视频,解迷教程(竟然有挂)该软件可以轻松地帮助玩...
必知教程!微乐小程序黑科技,7... 必知教程!微乐小程序黑科技,789大菠萝可以控制吗,积累教程(真的有挂)1、上手简单,内置详细流程视...
步骤辅助!竞技联盟破解版最新版... 步骤辅助!竞技联盟破解版最新版,hhpoker免费透视脚本,推荐教程(发现有挂)1、让任何用户在无需...
分享给玩家!创思维激k辅助工具... 分享给玩家!创思维激k辅助工具,约局吧辅助器,讲义教程(有挂总结)该软件可以轻松地帮助玩家将创思维激...
妙招辅助!德扑之心免费透视,w... 妙招辅助!德扑之心免费透视,wepoker怎么开辅助,辅助教程(有挂工具)1、玩家可以在德扑之心免费...
玩家必备教程!皮皮游戏辅助平台... 玩家必备教程!皮皮游戏辅助平台,朱雀开心罗松怎么开挂,资料教程(有挂透视)1、皮皮游戏辅助平台脚本辅...
总结辅助!hhpoker是正品... 总结辅助!hhpoker是正品吗,aapoker透视怎么用,解谜教程(有挂实锤)1)aapoker透...
今日重大通报!微信广东雀神挂件... 今日重大通报!微信广东雀神挂件辅助,随意玩辅助器,指南教程(有挂教学)1.微信广东雀神挂件辅助 选牌...
资料辅助!wepoker有辅助... 资料辅助!wepoker有辅助工具吗,wepoker软件安装包,教你教程(竟然有挂)小薇(辅助器软件...
实测分享!广西友乐免费辅助,新... 实测分享!广西友乐免费辅助,新荣耀辅助软件,指南书教程(有挂规律)1、玩家可以在广西友乐免费辅助透视...