三、linux字符驱动详解

news2025/2/23 17:57:27

在上一节完成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.coloradmin.cn/o/2304016.html

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

相关文章

谈谈 ES 6.8 到 7.10 的功能变迁(1)- 性能优化篇

前言 ES 7.10 可能是现在比较常见的 ES 版本。但是对于一些相迭代比较慢的早期业务系统来说&#xff0c;ES 6.8 是一个名副其实的“钉子户”。 借着工作内升级调研的任务东风&#xff0c;我整理从 ES 6.8 到 ES 7.10 ELastic 重点列出的新增功能和优化内容。将分为 6 个篇幅给…

我用Ai学Android Jetpack Compose之LinearProgressIndicator

本篇&#xff0c;我们来学习LinearProgressIndicator&#xff0c;答案来自 通义千问 Q:我想学习LinearProgressIndicator&#xff0c;麻烦你介绍一下 当然可以&#xff01;LinearProgressIndicator 是 Jetpack Compose 中的一个组件&#xff0c;用于显示线性进度条。它非常适…

在群晖上使用Docker安装思源笔记

​​ 最近一段时间&#xff0c;docker的镜像地址都失效了&#xff0c;在群晖系统中&#xff0c;无论是早期版本的docker&#xff0c;还是最新版本中的Container Manager&#xff0c;注册表中都无法链接到docker的镜像&#xff0c;于是&#xff0c;就花了点时间查找资料&#x…

【废物研究生刷算法】字符串

文章目录 1. 反转字符串2. 替换数字3. 反转字符串中的单词4. 右旋字符串总结1、字符串处理函数2、字符串切片 如果使用python处理字符串&#xff0c;有很多py内置的函数可以使用&#xff0c;主要还是记住这些处理方法。 1. 反转字符串 class Solution:def reverseStr(self, s, …

idea-代码补全快捷键

文章目录 前言idea-代码补全快捷键1. 基本补全2. 类型匹配补全3. 后缀补全4. 代码补全 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;…

我们来学人工智能 -- DeepSeek客户端

DeepSeek客户端 题记使用后记系列文章 题记 我选择了 Cherry Studio是国内产品由CherryHQ团队开源是一个平台在这里&#xff0c;有豆包、kimi、通义千问的入口当然&#xff0c;最主要是作为大模型的UI正如标题&#xff0c;这里&#xff0c;作为DeepSeep的客户端 使用 下载本…

洛谷 P1102 A-B 数对(详解)c++

题目链接&#xff1a;P1102 A-B 数对 - 洛谷 1.题目分析 2.算法原理 解法一&#xff1a;暴力 - 两层for循环 因为这道题需要你在数组中找出来两个数&#xff0c;让这两个数的差等于定值C就可以了&#xff0c;一层for循环枚举A第二层for循环枚举B&#xff0c;求一下看是否等于…

计算机视觉:主流数据集整理

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络(附代码) 第五章&#xff1…

2025软件测试面试常问的题(详细解析)

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件平…

项目POC的作用是什么

在项目管理和开发中&#xff0c;POC&#xff08;Proof of Concept&#xff0c;概念验证&#xff09;作为一个关键的步骤&#xff0c;扮演着非常重要的角色。POC的作用主要是验证某个概念、技术或方案的可行性&#xff0c;通过小规模实验或原型验证项目的关键假设&#xff0c;帮…

集合 数据结构 泛型

文章目录 1.Collection集合1.1数组和集合的区别【理解】1.2集合类体系结构【理解】1.3Collection 集合概述和使用【应用】内部类匿名内部类Lambda表达式 1.4Collection集合的遍历【应用】1.5增强for循环【应用】 2.List集合2.1List集合的概述和特点【记忆】2.2List集合的特有方…

vue-treeselect显示unknown的问题及解决

问题 解决办法 去node-modules包里面找到这个组件的源码&#xff0c;在它dist文件里面找到这个文件&#xff0c;然后搜索unknown&#xff0c;把它删掉就可以解决了。

代码随想录-训练营-day35

309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 这个题比起我们的买卖股票二来说多了一个冷冻期的说法&#xff0c;也就是我们卖出股票的第二天无法买入股票。 这样对我们而言&#xff0c;dp数组的含义&#xff0c;或者说dp数组中的状态显然就不能是…

【Blender】二、建模篇--06,曲线建模/父子级和蒙皮修改器

00:00:03,620 --> 00:00:09,500 前几节可能我们已经做了很多种类型的模型了 但是有一种类型 我们一直避开就是这种管道 1 00:00:10,050 --> 00:00:19,370 藤条头发啊 衣服架子啊这种弯弯绕绕的 需要一定柔软度的模型 那么这节课呢我们都来集中看一下曲线的模型 我们应该…

【服务器与本地互传文件】远端服务器的Linux系统 和 本地Windows系统 互传文件

rz 命令&#xff1a;本地上传到远端 rz 命令&#xff1a;用于从本地主机上传文件到远程服务器 rz 是一个用于在 Linux 系统中通过 串口 或 SSH 上传文件的命令&#xff0c;它实际上是 lrzsz 工具包中的一个命令。rz 命令可以调用一个图形化的上传窗口&#xff0c;方便用户从本…

被裁20240927 --- WSL-Ubuntu20.04安装cuda、cuDNN、tensorRT

cuda、cuDNN、tensorRT的使用场景 1. CUDA&#xff08;Compute Unified Device Architecture&#xff09; 作用&#xff1a; GPU 通用计算&#xff1a;CUDA 是 NVIDIA 的并行计算平台和编程模型&#xff0c;允许开发者直接利用 GPU 的并行计算能力&#xff0c;加速通用计算任…

【架构】微内核架构(Microkernel Architecture)

微内核架构(Microkernel Architecture) 核心思想 微内核架构(又称插件式架构)通过最小化核心系统,将可扩展功能以插件模块形式动态加载,实现高内聚低耦合。其核心设计原则: 核心最小化:仅封装基础通用能力(如插件管理、通信机制、安全校验)功能插件化:所有业务功能…

动静态链接与加载

目录 静态链接 ELF加载与进程地址空间&#xff08;静态链接&#xff09; 动态链接与动态库加载 GOT表 静态链接 对于多个.o文件在没有链接之前互相是不知到对方存在的&#xff0c;也就是说这个.o文件中调用函数的的跳转地址都会被设定为0&#xff08;当然这个函数是在其他.…

83_CentOS7通过yum无法安装软件问题解决方案

大家好,我是袁庭新。很多小伙伴在CentOS 7中使用yum命令安装软件时,出现无法安装成功的问题,今天给大家分享一套解决方案~ 在CentOS 7中,yum是一个常用的包管理工具,它基于RPM包管理系统。如果你发现yum无法使用,可能是由于多种原因造成的。以下是一些解决步骤,可以帮…

数据包在客户端和服务端,以及网络设备间如何传输的?

声明&#xff1a;文章中图片来自于网络收集&#xff0c;整体流程自己梳理。 目录 问题&#xff1a;如下socket客户端请求数据包如何传输的&#xff1f; 拓扑环境 数据包在分层间传输 网络分层L2/L3/L4 数据包收发-在各分层间变化 各层头部中-核心信息 数据包在不同设备…