三、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.mzph.cn/web/70492.shtml

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

相关文章

Python爬虫处理网页中的动态内容

文章目录 前言一、Python环境搭建1.Python安装2.选择Python开发环境 二、Python爬虫处理网页中的动态内容1. 使用 Selenium 库2. 使用 Pyppeteer 库3. 分析 API 请求 前言 在网页中&#xff0c;动态内容通常是指那些通过 JavaScript 在页面加载后动态生成或更新的内容&#xf…

重学SpringBoot3-Spring Retry实践

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞??收藏评论 重学SpringBoot3-Spring Retry实践 1. 简介2. 环境准备3. 使用方式 3.1 注解方式 基础使用自定义重试策略失败恢复机制重试和失败恢复效果注意事项 3.2 编程式使用3.3 监听重试过程 监…

vue3中解决组件间 css 层级问题最佳实践(Teleport的使用)

定义&#xff1a; <Teleport> 是 Vue 3 中引入的一个内置组件&#xff0c;用于将组件的内容渲染到 DOM 中的指定位置&#xff0c;而不受组件层级结构的限制。这在处理模态框、通知、下拉菜单等需要脱离当前组件层级的情况下非常有用。 通俗来说&#xff0c;Teleport的功…

密度提升30%!Intel 18A工艺正式开放代工

快科技2月23日消息&#xff0c;Intel官方网站悄然更新了对于18A(1.8nm级)工艺节点的描述&#xff0c;称已经做好了迎接客户项目的准备&#xff0c;将在今年上半年开始流片&#xff0c;有需求的客户可以随时联系。 Intel宣称&#xff0c;这是在北美地区率先量产的2nm以下工艺节…

docker中常用的命令

一、服务命令 systemctl start docker.service 启动docker服务 systemctl stop docker.service 关闭docker服务 systemctl enable docker.service 设置docker服务开机启动 systemctl disable docker.service .禁止docker服务开机自启动 二、镜像命令 d…

架构师论文《智慧医疗系统中的数据集成与共享》

智慧医疗系统中的数据集成与共享 摘要 随着医疗信息化的发展&#xff0c;如何实现跨系统、跨机构的数据集成与共享成为智慧医疗建设的核心问题。2019年&#xff0c;我所在的医疗科技公司承接了某省卫生健康委员会主导的“区域医疗信息化平台”项目。该平台旨在整合区域内三甲医…

请求go构建缓存,go clean -cache

go clean -cache go 构建时会产生很多缓存&#xff0c; 一般是目录&#xff1a;/Users/xxx/Library/Caches/go-build 此目录README&#xff1a; This directory holds cached build artifacts from the Go build system. Run "go clean -cache" if the directory …

mybatis从接口直接跳到xml的插件

在使用 MyBatis(包括 MyBatis-Plus)时,如果你希望从接口方法直接跳转到对应的 XML 映射文件中的 SQL 语句定义,可以借助一些开发工具或插件来实现这一功能。以下是几种常见的方法和插件推荐: 方法一:使用 IDE 内置功能 IntelliJ IDEA IntelliJ IDEA 提供了对 MyBatis …

计算机视觉行业洞察--影像行业系列第一期

计算机视觉行业产业链的上下游构成相对清晰&#xff0c;从基础技术研发到具体应用场景的多个环节相对成熟。 以下是我结合VisionChina经历和行业龙头企业对计算机视觉行业产业链上下游的拆解总结。 上下游总结 上游产业链分为软硬件两类&#xff0c;视觉的硬件主要指芯片、…

Spring事务原理 二

在上一篇博文《Spring事务原理 一》中&#xff0c;我们熟悉了Spring声明式事务的AOP原理&#xff0c;以及事务执行的大体流程。 本文中&#xff0c;介绍了Spring事务的核心组件、传播行为的源码实现。下一篇中&#xff0c;我们将结合案例&#xff0c;来讲解实战中有关事务的易…

逻辑函数的神经网络实现

1.单层感知器实现基本逻辑函数 先给大家抛出一道例题 &#xff08;一&#xff09;种类 a.OR函数 目标&#xff1a;当至少一个输入为1时&#xff0c;输出1&#xff1b;否则输出0。 权重设置&#xff1a; 输入权重&#xff1a;所有 wi1&#xff08;i1,2,...,m&#xff09;。…

SF-HCI-SAP问题收集1

最近在做HCI的集成&#xff0c;是S4的环境&#xff0c;发现很多东西都跑不通&#xff0c;今天开始收集一下错误点 如果下图冲从0001变成0010&#xff0c;sfiom_rprq_osi表就会存数据&#xff0c;系统检查到此表就会报错&#xff0c;这个选项的作用就是自定义信息类型也能更新&a…

(面试经典问题之分布式锁)分布式锁的基本原理、作用以及实现

一、什么是分布式锁 分布式锁指的是在分布式场景中实现互斥类型的锁。 分布式是什么意思&#xff1f;分布式表示运行的节点可能在不同的机器或不同的网段中&#xff0c;节点间通信通过socket。互斥类型是什么意思&#xff1f;互斥类型表示同一时刻只允许一个执行体进入临界资…

机械硬盘与固态硬盘的区别-机械硬盘的未来在哪里?

随着近年来固态硬盘的技术成熟和成本的下探&#xff0c;固态硬盘&#xff08;SSD&#xff09;俨然有要取代传统机械硬盘&#xff08;HDD&#xff09;的趋势&#xff0c;但目前单位容量下机械硬盘每GB价格相比闪存还有5-7倍的优势&#xff0c;那么机械硬盘是否已经发展到极限&am…

06排序 + 查找(D1_排序(D1_基础学习))

目录 学习预热&#xff1a;基础知识 一、什么是排序 二、为什么要排序 三、排序的稳定性 四、排序稳定性的意义 五、排序分类方式 方式一&#xff1a;内外分类 方式二&#xff1a;比较分类 六、排序算法性能评估 1. 算法的时间复杂度 2. 算法的空间复杂度 七、知识小…

简讯:Rust 2024 edition and v1.85.0 已发布

详见 https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html 升级方法&#xff1a;rustup update stable

Python 错误和异常处理

目录 try-except块 例子&#xff1a; 输出&#xff1a; 捕获多种异常 例子&#xff1a; else和finally 例子&#xff1a; 输出&#xff1a; 自定义异常 例子&#xff1a; 输出&#xff1a; 好的&#xff0c;简单来说&#xff0c;错误和异常处理是编程中用来处理程序…

Linux系统使用Docker部署Geoserver并做数据挂载进行地图服务的发布和游览

文章目录 1、前提环境2、拉取geoserver镜像3、创建数据挂载目录4、 运行容器5、 测试使用&#xff08;发布shp数据为服务&#xff09;5.1、创建工作区5.2、添加数据存储5.3、发布图层5.4、服务游览 1、前提环境 部署环境&#xff1a;Linux&#xff0c;Centos7 &#xff0c;Doc…

Innovus中快速获取timing path逻辑深度的golden脚本

在实际项目中我们经常会遇到一条timing path级数特别多&#xff0c;可能是一两页都翻不完。此时&#xff0c;我们大都需要手工去数这条path上到底有哪些是设计本身的逻辑&#xff0c;哪些是PR工具插入的buffer和inverter。 数字IC后端手把手培训教程 | Clock Gating相关clock …

Python爬虫实战:从零到一构建数据采集系统

文章目录 前言一、准备工作1.1 环境配置1.2 选择目标网站 二、爬虫实现步骤2.1 获取网页内容2.2 解析HTML2.3 数据保存 三、完整代码示例四、优化与扩展4.1 反爬应对策略4.2 动态页面处理4.3 数据可视化扩展 五、注意事项六、总结互动环节 前言 在大数据时代&#xff0c;数据采…