三、linux字符驱动详解

在上一节完成NFS开发环境的搭建后,本节将探讨Linux字符设备驱动的开发。字符设备驱动作为Linux内核的重要组成部分,主要负责管理与字符设备(如串口、键盘等)的交互,并为用户空间程序提供统一的读写操作接口。

驱动代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/cdev.h> #define DEVICE_NAME "hello_chrdev"
#define BUFFER_SIZE 100// 设备结构体
typedef struct {char buffer[BUFFER_SIZE];struct class *class;struct device *device;dev_t dev_num;struct cdev cdev;
} HelloDevice;static HelloDevice hello_dev;// 打开设备
static int hello_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Hello device opened\n");return 0;
}// 读取设备
static ssize_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {size_t len = strlen(hello_dev.buffer);if (*f_pos >= len) {return 0;}if (count > len - *f_pos) {count = len - *f_pos;}if (copy_to_user(buf, hello_dev.buffer + *f_pos, count)) {return -EFAULT;}*f_pos += count;return count;
}// 写入设备
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {if (count > BUFFER_SIZE - 1) {count = BUFFER_SIZE - 1;}if (copy_from_user(hello_dev.buffer, buf, count)) {return -EFAULT;}hello_dev.buffer[count] = '\0';*f_pos += count;return count;
}// 关闭设备
static int hello_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Hello device closed\n");return 0;
}// 文件操作结构体
static struct file_operations hello_fops = {.owner = THIS_MODULE,.open = hello_open,.read = hello_read,.write = hello_write,.release = hello_release,
};// 模块初始化函数
static int __init hello_init(void) {int ret;// 分配设备号ret = alloc_chrdev_region(&hello_dev.dev_num, 0, 1, DEVICE_NAME);if (ret < 0) {printk(KERN_ERR "Failed to allocate character device number\n");return ret;}// 创建类hello_dev.class = class_create(THIS_MODULE, DEVICE_NAME);if (IS_ERR(hello_dev.class)) {unregister_chrdev_region(hello_dev.dev_num, 1);printk(KERN_ERR "Failed to create class\n");return PTR_ERR(hello_dev.class);}// 创建设备hello_dev.device = device_create(hello_dev.class, NULL, hello_dev.dev_num, NULL, DEVICE_NAME);if (IS_ERR(hello_dev.device)) {class_destroy(hello_dev.class);unregister_chrdev_region(hello_dev.dev_num, 1);printk(KERN_ERR "Failed to create device\n");return PTR_ERR(hello_dev.device);}// 初始化 cdev 结构体cdev_init(&hello_dev.cdev, &hello_fops);hello_dev.cdev.owner = THIS_MODULE;// 添加字符设备到系统ret = cdev_add(&hello_dev.cdev, hello_dev.dev_num, 1);if (ret < 0) {device_destroy(hello_dev.class, hello_dev.dev_num);class_destroy(hello_dev.class);unregister_chrdev_region(hello_dev.dev_num, 1);printk(KERN_ERR "Failed to add character device\n");return ret;}printk(KERN_INFO "Hello device initialized. Major: %d, Minor: %d\n", MAJOR(hello_dev.dev_num), MINOR(hello_dev.dev_num));return 0;
}// 模块卸载函数
static void __exit hello_exit(void) {cdev_del(&hello_dev.cdev);device_destroy(hello_dev.class, hello_dev.dev_num);class_destroy(hello_dev.class);unregister_chrdev_region(hello_dev.dev_num, 1);printk(KERN_INFO "Hello device removed\n");
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple hello world character device driver");

函数接口详解

1. 模块初始化与退出相关函数
alloc_chrdev_region
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
  • 功能:动态分配一组连续的字符设备号。
  • 参数
    • dev:用于存储分配到的设备号。
    • baseminor:起始的次设备号。
    • count:要分配的设备号数量。
    • name:设备的名称,用于在 /proc/devices 中显示。
  • 返回值:成功返回 0,失败返回负数错误码。
class_create
struct class *class_create(struct module *owner, const char *name);
  • 功能:在 /sys/class 目录下创建一个设备类。
  • 参数
    • owner:指向模块的指针,通常为 THIS_MODULE
    • name:类的名称。
  • 返回值:成功返回指向 struct class 的指针,失败返回错误指针。
device_create
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
  • 功能:在 /sys/class/<class_name> 目录下创建设备节点,并在 /dev 目录下创建对应的设备文件。
  • 参数
    • class:指向设备类的指针。
    • parent:父设备指针,通常为 NULL
    • devt:设备号。
    • drvdata:设备驱动数据,通常为 NULL
    • fmt:设备名称的格式化字符串。
  • 返回值:成功返回指向 struct device 的指针,失败返回错误指针。
cdev_init
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
  • 功能:初始化字符设备结构体 struct cdev,并关联文件操作结构体 struct file_operations
  • 参数
    • cdev:指向 struct cdev 的指针。
    • fops:指向 struct file_operations 的指针。
cdev_add
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
  • 功能:将字符设备添加到内核中。
  • 参数
    • p:指向 struct cdev 的指针。
    • dev:设备号。
    • count:设备数量。
  • 返回值:成功返回 0,失败返回负数错误码。
module_initmodule_exit
module_init(hello_init);
module_exit(hello_exit);
  • 功能:分别指定模块加载和卸载时调用的函数。
2. 文件操作相关函数
  • 在 Linux 内核中,struct file_operations 结构体是字符设备驱动与用户空间进行交互的关键桥梁,其中 openreadwriterelease 是比较常用的操作函数。

    struct file_operations` 结构体中相关成员介绍

    open 函数
    int (*open) (struct inode *inode, struct file *filp);
    
    • 功能:当用户空间使用 open() 系统调用打开设备文件时,内核会调用驱动中注册的 open 函数。该函数通常用于执行设备的初始化操作,如分配资源、检查设备状态等。
    • 参数
      • struct inode *inode:指向文件对应的索引节点,包含了文件的元信息,如文件类型、权限等。
      • struct file *filp:指向文件对象,代表了一个打开的文件实例,包含了文件的当前状态、偏移量等信息。
    • 返回值:成功时返回 0,失败时返回负数错误码。
    read 函数
    ssize_t (*read) (struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
    
    • 功能:当用户空间使用 read() 系统调用从设备文件读取数据时,内核会调用驱动中的 read 函数。该函数负责将设备中的数据复制到用户空间的缓冲区。
    • 参数
      • struct file *filp:指向文件对象。
      • char __user *buf:用户空间的缓冲区指针,用于存储从设备读取的数据。
      • size_t count:用户请求读取的字节数。
      • loff_t *f_pos:文件的当前偏移量指针,可通过修改该指针来更新文件的读写位置。
    • 返回值:成功时返回实际读取的字节数,返回 0 表示已到达文件末尾,失败时返回负数错误码。
    write 函数
    ssize_t (*write) (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
    
    • 功能:当用户空间使用 write() 系统调用向设备文件写入数据时,内核会调用驱动中的 write 函数。该函数负责将用户空间缓冲区中的数据复制到设备中。
    • 参数
      • struct file *filp:指向文件对象。
      • const char __user *buf:用户空间的缓冲区指针,包含了要写入设备的数据。
      • size_t count:用户请求写入的字节数。
      • loff_t *f_pos:文件的当前偏移量指针。
    • 返回值:成功时返回实际写入的字节数,失败时返回负数错误码。
    release 函数
    int (*release) (struct inode *inode, struct file *filp);
    
    • 功能:当用户空间使用 close() 系统调用关闭设备文件时,内核会调用驱动中的 release 函数。该函数通常用于执行设备的清理操作,如释放资源、关闭设备等。
    • 参数
      • struct inode *inode:指向文件对应的索引节点。
      • struct file *filp:指向文件对象。
    • 返回值:成功时返回 0,失败时返回负数错误码。
3. 模块卸载相关函数
cdev_del
void cdev_del(struct cdev *p);
  • 功能:从内核中移除字符设备。
  • 参数
    • p:指向 struct cdev 的指针。
device_destroy
void device_destroy(struct class *class, dev_t devt);
  • 功能:销毁 /sys/class/<class_name> 目录下的设备节点和 /dev 目录下的设备文件。
  • 参数
    • class:指向设备类的指针。
    • devt:设备号。
class_destroy
void class_destroy(struct class *cls);
  • 功能:销毁 /sys/class 目录下的设备类。
  • 参数
    • cls:指向 struct class 的指针。
unregister_chrdev_region
void unregister_chrdev_region(dev_t from, unsigned count);
  • 功能:释放之前分配的字符设备号。
  • 参数
    • from:起始的设备号。
    • count:要释放的设备号数量。

编译和测试

编写 Makefile
obj-m += helloworld.o
KDIR := linux-5.15.18/
PWD := $(shell pwd)
default:$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:$(MAKE) -C $(KDIR) M=$(PWD) clean

将 linux-5.15.18/ 替换为实际的 Linux 5.15.18 内核源码路径。

编译驱动

在终端中执行 make 命令编译驱动模块。

测试驱动

在 QEMU 终端中:

  1. 使用 insmod helloworld.ko 加载驱动模块。
  2. 使用 echo “Hello World” > /dev/helloworld 向设备写入数据。
  3. 使用 cat /dev/helloworld 从设备读取数据。
  4. 使用 rmmod hello_chrdev.ko 卸载驱动模块。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/22759.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[SQL] 事务的四大特性(ACID)

&#x1f384;事务的四大特性 以下就是事务的四大特性&#xff0c;简称ACID。 原子性&#x1f4e2;事务时不可分割的最小操作单元&#xff0c;要么全部成功&#xff0c;要么全部失败。一致性&#x1f4e2;事务完成后&#xff0c;必须使所有的数据都保持一致隔离性&#x1f4e2…

如何使用3D高斯分布进行环境建模

使用3D高斯分布来实现建模&#xff0c;主要是通过高斯分布的概率特性来描述空间中每个点的几何位置和不确定性。具体来说&#xff0c;3D高斯分布被用来表示点云数据中的每一个点或体素&#xff08;voxel&#xff09;的空间分布和不确定性&#xff0c;而不是单纯地存储每个点的坐…

蓝桥杯笔记——递归递推

递归 0. 函数的概念 我们从基础讲起&#xff0c;先了解函数的概念&#xff0c;然后逐步引入递归&#xff0c;帮助同学们更好地理解递归的思想和实现方式。 函数是程序设计中的一个基本概念&#xff0c;简单来说&#xff0c;它是一段封装好的代码&#xff0c;可以在程序中多次…

C++ IDE设置 visual studio 2010安装、注册、使用

Visual Studio 2010 C学习版 系列教程_哔哩哔哩_bilibiliVisual Studio 2010 C学习版 系列教程共计16条视频&#xff0c;包括&#xff1a;Visual Studio C 2010学习版 安装教程、Visual Studio C 2010学习版 激活方法、Visual Studio C 2010学习版 软件使用教学等&#xff0c;U…

细说Java 引用(强、软、弱、虚)和 GC 流程(一)

一、引用概览 1.1 引用简介 JDK1.2中引入了 Reference 抽象类及其子类&#xff0c;来满足不同场景的 JVM 垃圾回收工作&#xff1a; SoftReference 内存不足&#xff0c;GC发生时&#xff0c;引用的对象&#xff08;没有强引用时&#xff09;会被清理&#xff1b;高速缓存使用…

win11系统无法打开软件_组策略无法打开_gpedit.msc不生效_为了对电脑进行保护,已经阻止此应用---Windows工作笔记057

碰到这个问题挺麻烦的,要用的软件打不开了. 其实解决方法就是去组策略中修改一个策略就可以了,但是: 先来说: 而且,使用cmd输入的gpedit.msc也打不开了. 这个怎么解决? @echo off pushd "%~dp0"dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPo…

算法日记23:SC16+17(求数的因子+质因子)

题目1&#xff1a; 求解因子 题解1&#xff1a; 1&#xff09;易得&#xff0c;当 n a ∗ b na*b na∗b时&#xff0c; a , b {a,b} a,b是n的因子(假设 a < b a<b a<b) 可以发现只需枚举到即可 n \sqrt{n} n ​&#xff0c;因为 a < n < b a<\sqrt{n}&l…

欢乐力扣:同构字符串

文章目录 1、题目描述2、 代码 1、题目描述 同构字符串。给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。  每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符…

【HeadFirst系列之HeadFirst设计模式】第7天之命令模式:封装请求,轻松实现解耦!

命令模式&#xff1a;封装请求&#xff0c;轻松实现解耦&#xff01; 大家好&#xff01;今天我们来聊聊设计模式中的命令模式&#xff08;Command Pattern&#xff09;。如果你曾经需要将请求封装成对象&#xff0c;或者希望实现请求的撤销、重做等功能&#xff0c;那么命令模…

敏捷开发07:敏捷项目可视化管理-ScrumBoard(Scrum板)使用介绍

ScrumBoard(Scrum板)介绍 ScrumBoard&#xff08;Scrum板&#xff09;是敏捷项目管理中使用的可视化工具&#xff0c;用于跟踪和监控冲刺阶段的任务进度。 主要通过可视化的看板来管理工作&#xff0c;它可视化了敏捷开发中的工作流程、任务状态、团队角色。 Scrum 团队在各…

Linux第十三节 — 进程状态详解

只要一个进程的PCB还存在内存当中&#xff0c;哪怕此时该进程对应的代码和数据已经在磁盘当中&#xff0c;此时依然认为该进程仍然存在&#xff01; 一、Linux进程的运行状态R 接下来我们看下面这个例子&#xff1a; 当我们执行这个程序的时候&#xff0c;我们认为该进程的状…

无人机遥控器接口作用详解!

USB接口&#xff1a; 功能&#xff1a;USB接口是一种通用串行总线接口&#xff0c;用于连接外部设备&#xff0c;如手机、平板、电脑或充电设备。在无人机遥控器上&#xff0c;USB接口通常用于数据传输和充电。 应用&#xff1a;用户可以通过USB接口将遥控器与电脑连接&#…

SVN把英文换中文

原文链接&#xff1a;SVN设置成中文版本 都是英文&#xff0c;换中文 Tortoise SVN 安装汉化教程(乌龟SVN) https://pan.quark.cn/s/cb6f2eee3f90 下载中文包

云手机如何进行经纬度修改

云手机如何进行经纬度修改 云手机修改经纬度的方法因不同服务商和操作方式有所差异&#xff0c;以下是综合多个来源的常用方法及注意事项&#xff1a; 通过ADB命令注入GPS数据&#xff08;适用于技术用户&#xff09; 1.连接云手机 使用ADB工具连接云手机服务器&#xff0c;…

【微服务优化】ELK日志聚合与查询性能提升实战指南

网罗开发 &#xff08;小红书、快手、视频号同名&#xff09; 大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等…

transfmer学习认识

整体架构 1.自注意机制 1.1.softmax 在机器学习和深度学习中&#xff0c;softmax 函数是一个常用的激活函数&#xff0c;用于将一个向量转换为一个概率分布。softmax 函数的公式如下&#xff1a; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/35c158988402498ba6…

在 macOS 的 ARM 架构上按住 Command (⌘) + Shift + .(点)。这将暂时显示隐藏文件和文件夹。

在 macOS 的 ARM 架构&#xff08;如 M1/M2 系列的 Mac&#xff09;上&#xff0c;设置 Finder&#xff08;访达&#xff09;来显示隐藏文件夹的步骤如下&#xff1a; 使用快捷键临时显示隐藏文件&#xff1a; 在Finder中按住 Command (⌘) Shift .&#xff08;点&#xff…

【HarmonyOS NEXT星河版开发实战】天气查询APP

目录 前言 界面效果展示 首页 添加和删除 界面构建讲解 1. 获取所需数据 2. 在编译器中准备数据 3. index页面代码讲解 3.1 导入模块&#xff1a; 3.2 定义组件&#xff1a; 3.3 定义状态变量: 3.4 定义Tabs控制器: 3.5 定义按钮样式&#xff1a; 3.6 页面显示时触发…

idea debug功能演示线程安全问题

概述 用idea debug功能演示上一篇博客中提到的 本实现中的出队、入队的实现逻辑会不会有线程安全问题&#xff1f;如果有&#xff0c;怎么解决&#xff1f; 测试用例 package com.lovehena.datastructure.test;import com.lovehena.datastructure.ArrayQueue;/* * 测试 offer…

力扣每日一题【算法学习day.132】

前言 ###我做这类文章一个重要的目的还是记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&#xff01;&#xff01;&#xff01; 习题 1.统计相似字符串对的数目 题目链…